Repository navigation
docs(service-datasource): re-anchor the dead tracker citations to the commits that decided them - #20693
Conversation
… commits that decided them Every comment and docblock site under packages/services/service-datasource/src that cited a tracker number answering 404 now cites the commit in this repository's history that decided what the line describes, and says in its own words what that commit decided (ruling C+D, form C): 75 sites on 74 lines in 23 files, 16 numbers, 17 commits, plus 4 reflow lines. Comments only; every file keeps its line count, and no citation number is added. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
The rewritten docblocks ship in dist (index.d.ts / index.d.cts, and the runtime bundles for the ones tsup keeps), so the released package changes bytes and owes a patch changeset. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check8 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 1 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin df43dd03b7714d4619b3a808246369755c4ef92c && git checkout df43dd03b7714d4619b3a808246369755c4ef92c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 14f80e23957165f6fb23c2b3d59bdc7668652dfb 265dc6861ea8235e3fe5962fd814276c391a6ad2 && git checkout -B drift-repro 14f80e23957165f6fb23c2b3d59bdc7668652dfb && git merge --no-ff 265dc6861ea8235e3fe5962fd814276c391a6ad2
node scripts/docs-audit/affected-docs.mjs --json 14f80e23957165f6fb23c2b3d59bdc7668652dfb |
…mmits that decided them (objectstack-ai#20708) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the sixth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-storage/src/**` and nothing else. By the seat's census at the claim (`5896394242`), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 5 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`). That is **42 sites on 41 lines in 15 files, covering 8 numbers**: - 27 census sites (every census site this package has); - 15 sites in test comments, which the census defers. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **7 distinct shas**. No number in this package has an ADR or ruling record of its own in the repository (a grep of `docs/adr/` for all 8 finds none, and the repository keeps no other ruling-record file for them), so every anchor is a commit, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (43 lines out, 43 in, over 15 files), so no line citation into these files moves. 2 of those 43 lines hold no dead citation; they are reflow, listed under Wordings below. No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces: `objectstack-ai#12069` (`translations/index.ts:29`), `objectstack-ai#10246` (`storage-service-plugin.ts:392`) and the cross-repo `cloud#1395` (`backfill-sys-file-organizations.ts:86`). Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number stands on an added line. Eleven dead sites are left on purpose, all of them test titles (see the list below). One more file: a `patch` changeset for `@objectstack/service-storage`, because the rewritten docblocks and inline comments ship (see Changeset below). ## Census: `service-storage`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/services/service-storage/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | service-storage sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `31ed06763`, run 2026-09-29T18:42:47Z to 18:46:12Z | enumerated, 186 pages, frontier objectstack-ai#20702 (newest objectstack-ai#20702 before and after), 18,529 numbers | 1,254 | **27** | 27 | 8 | 8 | | after | head `5db5155a2`, run 18:55:34Z to 18:58:50Z | enumerated, 186 pages, frontier objectstack-ai#20702 (newest objectstack-ai#20702 before and after), 18,529 numbers | 1,227 | **0** | 0 | 0 | 0 | The before count matches the seat's census at the claim (27 sites in 8 files, at `6bff748b`). The whole-repo drop is 27, exactly this diff's census sites. The `resolves` tally is 32,967 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (994) did not move either. The after run was taken on `5db5155a2`; the head `09d2ecc96` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `service-storage/src` (71 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction, 4,943 numbers) and did not report it. The three numbers the census never saw, because they stand only in test files (`objectstack-ai#13996`, `objectstack-ai#15607`, `objectstack-ai#17571`), were read one by one on the issues endpoint, and each answers 200. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `31ed06763` | 608 | **53** | 27 | 15 | 0 | 11 | | after, `5db5155a2` | 566 | **11** | 0 | 0 | 0 | 11 | Its src-comment column equals the census's 27, which is the control on the second instrument. The 554 live citations and the 1 cross-repo citation are the same in both readings, and the drop of 42 citations is exactly the rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 53 dead occurrences before and 11 after, and its residue equals the gate's residue site for site. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts each rewritten line in that commit or in a later one that applied it. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#13178` | 19/5 | 14/5 | `f087c376f`: the `sys_file` / `sys_upload_session` update and delete doors take the acting organization and scope the statement to it (they stamp nothing), and the upload routes bind the session they had resolved and discarded. New to the sweep | | `objectstack-ai#13279` | 11/4 | 11/0 | `6a180e42d`: a failed permission-store read raises `AuthzStoreUnavailableError` (503) instead of reading as zero grants, and the transports' fail-closed nets, this package's file-read authorizer among them, re-raise it. The anchor of stages 2 and 5 and of the rest, runtime and types stages | | `objectstack-ai#10091` | 9/3 | 5/4 | `da891e0ef`: `sys_attachment` `beforeUpdate` gated by the uploader-or-parent-editor rule, the attach rule on a re-point, and the update-verb refusal of an unscoped multi-update. New to the sweep | | `objectstack-ai#11427` | 6/3 | 4/2 | `c3c72a4bc`: record file-field hydration asks the reap guard's held-file question, through the batched `findHeldFiles` this package adds, so hydration and the download path agree about a tombstoned `sys_file`. Its message ends with a reference to `objectstack-ai#11427`. New to the sweep | | `objectstack-ai#6206` | 3/2 | 3/0 | `aa4b90d9a`: the full-envelope ruling applied to the sharing contract; `ISharingService` takes the whole `ExecutionContext`, and its docblock says callers "MUST NOT rebuild a subset of it". Stage 2's anchor, named there as the full-envelope ruling | | `objectstack-ai#6523` | 3/2 | 3/0 | `aa4b90d9a`: the same commit, which was `objectstack-ai#6523`'s change (its subject names it). Stage 2's and the spec stage's anchor | | `objectstack-ai#8778` | 1/1 | 1/0 | `7901b2dd2`: stamp-only `tenancy.organizationField`, with its consumers scope-pinned by the maintainer's ruling (the pin text is in its diff). The spec and `plugin-security` stages' anchor | | `objectstack-ai#11671` | 1/1 | 1/0 | `09b4f4e4e`: the source-hashes provenance companion. The identical `translations/index.ts` line in `service-messaging`, `plugin-sharing` and `plugin-security` already cites it | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 7), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 7; the history is complete, `--is-shallow-repository` false, 15,120 commits). Each of the 8 numbers answers 404 on the issues endpoint, read one by one before the rewrite. ## Wordings to check - **The full-envelope ruling, `attachment-access-hooks.ts:127` and `:129`.** 「what the objectstack-ai#6206 ruling requires … (objectstack-ai#6523)」 became 「what the full-envelope ruling requires … (commit aa4b90d)」. The quoted words 「MUST NOT rebuild a subset of it」 are the `ISharingService` docblock that `aa4b90d9a` wrote, so the commit sits beside the quotation. The same form at `attachment-access-hooks.test.ts:766`. - **`attachment-access-hooks.test.ts:914-916`.** 「the objectstack-ai#6523 contract's unit is the envelope, and objectstack-ai#6206 forbids rebuilding a subset of it」 became 「the contract's unit is the envelope (commit aa4b90d), and the full-envelope ruling forbids rebuilding a subset of it」 (1 reflow line, `:916`). - **A heading that named its card, `attachment-access-hooks.test.ts:621`.** 「objectstack-ai#10091 through the WIRED engine」 became 「Commit da891e0's gate through the WIRED engine」. - **The confusion the loud outage prevents,** `storage-routes.ts:201`, `storage-service-plugin.ts:1057`, `file-read-tenancy-posture-admission.test.ts:582` and `storage-routes.authz-outage-relay.test.ts:16`. 「the confusion objectstack-ai#13279 exists to prevent」 became 「the confusion commit 6a180e4 was made to prevent」: an outage answered as a capability denial is what that commit's message says it removes. - **The relay, `storage-service-plugin.ts:1048` and `:1149`.** 「the objectstack-ai#13279 relay that block already runs」 became 「the relay that block has run since commit 6a180e4」, and 「takes the objectstack-ai#13279 relay in」 became 「takes the relay (commit 6a180e4) in」. The re-raise in that `catch` (`:1229`) is in `6a180e42d`'s diff. - **A referent, `storage-service-plugin.ts:1058-1059`.** 「it had swallowed the objectstack-ai#13279 permission-store outage at this door since that card landed」 became 「it had swallowed the branded permission-store outage at this door since commit 6a180e4 landed」: 「that card」 lost its referent with the number (1 reflow line, `:1059`). - **The update/delete halves, `file-reference-lifecycle.ts:111`.** 「the update/delete halves objectstack-ai#13178)」 became 「the update/delete halves in commit f087c37)」, beside the live `objectstack-ai#12745` and `objectstack-ai#12928`. - **Present tense made past, `tombstone-hydration-download-agreement.test.ts:320`.** 「the divergence objectstack-ai#11427 fixes」 became 「the divergence commit c3c72a4 fixed」. - **The scope pin, `backfill-sys-file-organizations.ts:86`.** 「scope-pinned by the objectstack-ai#8778 ruling (widened by name on cloud#1395)」 became 「scope-pinned by its ruling (commit 7901b2d; widened by name on cloud#1395)」. 「its」 is the key's own ruling, which `7901b2dd2` carried out and recorded as the pin; the widening is the cross-repo reference that was already there. - **Reflow, 2 lines with no dead site** (every file keeps its line count): `attachment-access-hooks.test.ts:916`, `storage-service-plugin.ts:1059`. ## The 11 sites left - **Test titles, 11 sites.** `describe` / `it` titles, which are string tokens, left as stages 1 to 5 left theirs: `attachment-access-hooks.test.ts:232`, `:314`, `:640`, `:932` (`objectstack-ai#10091`); `tenant-audit-update-delete-half-repairs.test.ts:151`, `:224`, `:345`, `:552`, `:664` (`objectstack-ai#13178`); `tombstone-hydration-download-agreement.test.ts:148`, `:326` (`objectstack-ai#11427`). - There is no operator string, assertion message, generated header or quoted ruling carrying a dead number in this package. The generated `*.source-hashes.generated.ts` headers are untouched and carry none. The verbatim maintainer quotations in scope (5 lines: 「同意」 three times, 「12745 A回,其他同意。」 and 「批 objectstack-ai#7 同意」) carry no dead number and are untouched. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base `31ed06763` against head. Template literals are therefore read in context. It ran over all 15 touched `.ts` files. - Real run: 20,143 base leaf tokens, **0 files with a token change** (exit 0). - Comment control in `storage-routes.ts` (`Bound, not discarded` to `Bound and not discarded`): 0 files changed, as expected (exit 0). - Positive control, a code token added in `storage-routes.ts` (`const { fileId, eTag } = req.body ?? {};` given a trailing `?? undefined`): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`tombstone-hydration-download-agreement.test.ts:148`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`44ecc8e64ae0`, `ee84cf718a6f`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-storage` (`.changeset/20596-service-storage-provenance-anchors.md`) is included. It says only that the provenance comments were re-anchored, in stage 5's words. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build, the rewritten comments reach `dist`: `f087c376f` 6 times and `da891e0ef` once in each of `dist/index.d.ts` and `index.d.cts`; `f087c376f` 4 times and `da891e0ef` once in each of `index.js` and `index.cjs`. Positive controls: the unchanged line 「the parent record — the delete rule, applied to the verb that could」 beside the shipped rewrite at `attachment-access-hooks.ts:28` is found once in each declaration file, and the unchanged line 「standard catalog code — the same both-verbs pairing the derived」 beside the shipped rewrite at `:470` once in each JS file. A never-written negative phrase appears nowhere in `dist`. None of the 8 dead numbers is left anywhere in `dist`. ## Gates (head `09d2ecc96`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 3 citations (`objectstack-ai#12069` and `objectstack-ai#10246` resolve; `cloud#1395` is cross-repo), each already on the line it replaces. - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `09d2ecc96` derived 65 commands: all 56 derived at dispatch, plus `check:duration-unit-keys`, `check:dispatcher-error-vocabulary`, `check:engine-double-contract`, `check:logger-receiver-detach`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. Each ran with its exit code captured before any pipe, and all 65 exit 0. `--ran`, fed each command with its exit code, reports 65 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/service-storage test`: 40 files pass and 627 tests pass. That is every test file in the package, the 7 touched ones included. - `pnpm --filter @objectstack/service-storage typecheck` exits 0 (`tsc` on `tsconfig.json`, the scripts program, and the test layer on `tsconfig.test.json`). `--listFiles` on both `tsconfig.json` and `tsconfig.test.json` shows all 71 files under `src/`, the 40 test files included, and all 15 touched files in the program. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 15 touched `.ts` files gives 15 files, 0 errors and 0 warnings. All 15 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 16 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636). In this package there is no `#N-word` spelling at all. There are 11 `#A/#B` lines carrying 13 second numbers (`attachment-access-hooks.ts:215`, `:217`, `:434`; `attachment-access-hooks.test.ts:217`; `attachment-lifecycle.ts:177`; `file-reference-lifecycle.test.ts:226`; `local-storage-adapter.test.ts:35`; `metadata-store.test.ts:41`; `storage-route-ledger.ts:74`, which chains four; `storage-routes.metadata-outage.test.ts:67`; `tombstone-download-live-reference.test.ts:50`), and every second number on them is live: `objectstack-ai#5574`, `objectstack-ai#9974`, `objectstack-ai#5541`, `objectstack-ai#5480`, `objectstack-ai#3833` and `objectstack-ai#3847` by the census's own board, and `objectstack-ai#5197` and `objectstack-ai#3870` read one by one (200). So nothing there needed rewriting. The claim counted 12 such spellings on `main`; this reading is 11 lines and 13 second numbers, with nothing dead among them either way. The raw scan above, which sees both spellings, agrees. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#13178` → `f087c376f`; `objectstack-ai#10091` → `da891e0ef`; `objectstack-ai#11427` → `c3c72a4bc`; `objectstack-ai#13279` → `6a180e42d`; `objectstack-ai#6206` / `objectstack-ai#6523` → `aa4b90d9a`; `objectstack-ai#8778` → `7901b2dd2`; `objectstack-ai#11671` → `09b4f4e4e`. - **Base.** The branch is 4 commits behind `main` (`defc7f7b5`, read at 19:31Z). None touches `service-storage`, `scripts/check-issue-citations.mjs` or `.changeset/config.json`, so there was no merge. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ommits that decided them (objectstack-ai#20717) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the seventh stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/plugins/plugin-approvals/src/**` and nothing else. By the seat's census at the claim (`5897866351`), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 6 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`). That is **41 sites on 38 lines in 13 files, covering 14 numbers**: - 24 census sites (every census site this package has); - 15 sites in test comments, which the census defers; - 2 sites the gate's citation grammar cannot see, found by a raw scan (see Acceptance notes): the second number of `objectstack-ai#8287/objectstack-ai#8778` (`approval-node.test.ts:462`) and 「the option objectstack-ai#8710 rejected」 (`approval-service.ts:2329`), which the gate reads as an option ordinal. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **13 distinct shas**. No number in this package has an ADR or ruling record of its own in the repository (a grep of `docs/adr/` for all 14 finds none, and a grep of the rest of `docs/` finds only an audit that names `objectstack-ai#11311` as evidence), so every anchor is a commit, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (41 lines out, 41 in, over 13 files), so no line citation into these files moves. 3 of those 41 lines hold no dead citation: 1 reflow line and 2 lost-referent lines, listed under Wordings below. No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces: `objectstack-ai#8613` (`approval-service.ts:2295`, `:2363`, `approval-service.test.ts:751`), `objectstack-ai#8287` (`sys-approval-request.object.ts:138`, `approval-node.test.ts:462`), `objectstack-ai#10101` (`backfill-platform-row-organizations.ts:9`) and `objectstack-ai#12069` (`translations/index.ts:26`). Each answers 200. Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number stands on an added line; the one `PR #N` spelling in scope (`backfill-platform-row-organizations.ts:9`) became its squash commit. Five dead sites are left on purpose, all of them test strings (see the list below). One more file: a `patch` changeset for `@objectstack/plugin-approvals`, because the rewritten docblocks and inline comments ship (see Changeset below). ## Census: `plugin-approvals`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/plugins/plugin-approvals/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | plugin-approvals sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `575746371`, run 2026-09-29T20:15:05Z to 20:18:28Z | enumerated, 186 pages, frontier objectstack-ai#20709 (newest objectstack-ai#20709 before and after), 18,536 numbers | 1,195 | **24** | 22 | 6 | 10 | | after | head `e698d2393`, run 20:28:39Z to 20:31:58Z | enumerated, 186 pages, frontier objectstack-ai#20716 (newest objectstack-ai#20714 before, objectstack-ai#20716 after), 18,543 numbers | 1,171 | **0** | 0 | 0 | 0 | The before count matches the seat's census at the claim and A1 (24 sites). The whole-repo drop is 24, exactly this diff's census sites. The `resolves` tally is 32,971 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `e698d2393`; the head `708244c2b` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `plugin-approvals/src` (76 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction) and did not report it. The 18 numbers the census never saw, because they stand only in test files or strings here, were read one by one on the issues endpoint: 14 answer 200, and `objectstack-ai#8863`, `objectstack-ai#11081`, `objectstack-ai#11286` and `objectstack-ai#11308` answer 404. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `575746371` | 981 | **44** | 24 | 15 | 0 | 5 | | after, `e698d2393` | 942 | **5** | 0 | 0 | 0 | 5 | Its src-comment column equals the census's 24, which is the control on the second instrument. The 902 live citations and the 32 cross-repo citations are the same in both readings, and the drop of 39 citations is exactly the rewritten sites the gate grammar sees. Three extracted tokens are not citations and stay unjudged in both readings: `&objectstack-ai#39;` (an HTML entity) and two CSS colours, all in `action-link-pages.ts` string literals. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 46 dead occurrences before and 5 after; the 2 it sees beyond the gate are the two gate-invisible sites above, and its residue equals the gate's residue site for site. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (`merge-base --is-ancestor` exit 0 for each pair). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#16709` | 10/2 | 8/2 | `8c7cca1ce`: the three residues of the stranded-inspection contract review. Item 2 (the PM ruling of 2026-09-08) keeps a row whose third read threw in the report as the undifferentiated `failed`; item 3 moves `refineFailedRunState` inside the `try`, so a malformed host verdict costs only its own row. Its message numbers the items, which is why the lines keep 「item 2」 and 「item 3」. New to the sweep | | `objectstack-ai#8710` | 6/2 | 6/0 | `04d03c3a0`: a deactivated `sys_position` confers no sharing-rule shares, filtered at the sharing call site and never inside the addressing primitive. Its message quotes the 2026-08-15 ruling verbatim, the same sentence the quoted blocks here carry, and its diff writes the 「a name with no row is untouched」 fallback that `approval-service.ts:2318` quotes. Stage 2's anchor, and `plugin-sharing/src/position-graph.ts:42` already reads 「objectstack-ai#8613 / commit 04d03c3」 | | `objectstack-ai#6523` | 4/3 | 4/0 | `aa4b90d9a`: the 36 enforcement signatures, `IApprovalService` among them, converged onto the full `ExecutionContext`. Its subject names it. Stages 2 and 6 and the spec stage's anchor | | `objectstack-ai#6206` | 3/3 | 3/0 | `aa4b90d9a`: the same commit, whose body applies 「the objectstack-ai#6206 ruling default (converge on the full envelope, keep no per-site subset contracts)」. Written as the full-envelope ruling, the form stages 2 and 6 used | | `objectstack-ai#8778` | 4/4 | 4/0 | `7901b2dd2`: the stamp-only `tenancy.organizationField`, Option A of the maintainer's ruling, declared on `sys_api_key` as `active_organization_id`. The spec, `plugin-security` and `service-storage` stages' anchor | | `objectstack-ai#11081` | 5/1 | 5/0 | `c28e4cfae`: the two SqlDriver-backed fixtures of `objectstack-ai#11081` stop muting their kernel and pin the expected read-refusal noise with the runtime's shared capture. Its diff writes all five `[objectstack-ai#11081]` tags. New to the sweep | | `objectstack-ai#11286` | 5/1 | 2/3 | `b019891cd`: the contract test that pins the two `managerIsProvablyOutsideOrg` screens to equal verdicts. Its subject names it. New to the sweep | | `objectstack-ai#11674` | 2/1 | 2/0 | `1cba33f16`: the seed loader warns at load time when a seed defers a required column, and the ordering constraint is documented at the four pointer-pair sites, this object among them. Stage 2's anchor for the same paragraph | | `objectstack-ai#12493` | 2/2 | 2/0 | `aa5994e17`: the Operation Message Catalog gains `approval_recall_not_submitter` (and `record_write_denied`) ahead of their emitters. Its diff names `objectstack-ai#12493` throughout. Stage 2's anchor | | `objectstack-ai#8707` | 1/1 | 1/0 | `1408fe385`: audit rows are stamped from the record's own organization, which its message says the maintainer's ruling on `objectstack-ai#8287` requires; the line keeps 「honouring objectstack-ai#8287's ruling」. New to the sweep | | `objectstack-ai#8863` | 1/1 | 1/0 | `d200b016b`: the two negative pins that assert the unfiltered position expansion on the approvals side. Its body names `objectstack-ai#8863`. New to the sweep | | `objectstack-ai#11308` | 1/1 | 1/0 | `5a916c4d4`: the one-off platform-row organization backfill, dry run and write, which its body calls the `objectstack-ai#11308` sweep. New to the sweep | | `objectstack-ai#11311` | 1/1 | 1/0 | `1272f0a6b`: the squash commit of the pull request that was `objectstack-ai#11311` (its subject carries the number), which moved the resolver to `metadata-core` and made the approval and automation-run writers stamp the subject's organization. New to the sweep | | `objectstack-ai#11671` | 1/1 | 1/0 | `09b4f4e4e`: the source-hashes provenance companion. The identical `translations/index.ts` line in `service-messaging`, `plugin-sharing` and `plugin-security` already cites it | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 13), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 13; the history is complete, `--is-shallow-repository` false, 15,129 commits). Each of the 14 numbers answers 404 on the issues endpoint, read one by one; `objectstack-ai#11311` answers 404 on the pulls endpoint too. ## Wordings to check - **The full-envelope ruling, `approval-node.ts:29`, `approval-service.ts:52-53` and `exec-context-annotation.pin.ts:7-8`.** 「since objectstack-ai#6523 (the objectstack-ai#6206 ruling …)」 became 「since commit aa4b90d (the full-envelope ruling …)」, word for word the form `plugin-sharing`'s landed `sharing-service.ts:20` and `exec-context-annotation.pin.ts:7` use. `approval-service.ts:53` is 1 reflow line. - **The ruling's record, `approval-service.ts:2295` and `approval-service.test.ts:751`.** 「Maintainer ruling, 2026-08-15 (objectstack-ai#8710, inheriting objectstack-ai#8613), verbatim:」 became 「… (commit 04d03c3, inheriting objectstack-ai#8613), verbatim:」. The quotation under it is the ruling itself and is untouched; `04d03c3a0`'s message carries the same sentence. - **`approval-service.ts:2329`.** 「that is the option objectstack-ai#8710 rejected」 became 「that is the option the ruling (commit 04d03c3) rejected」. - **The test heading, `approval-service.test.ts:749`.** 「the objectstack-ai#8710 carve-out, asserted on THIS side (objectstack-ai#8863)」 became 「the commit 04d03c3 carve-out, asserted on THIS side (commit d200b01)」: the carve-out's record, and the commit that asserted it here. - **A PR number, `backfill-platform-row-organizations.ts:9`.** 「objectstack-ai#10101 (landed as PR objectstack-ai#11311)」 became 「objectstack-ai#10101 (landed as commit 1272f0a)」, the pull request's squash commit. - **Item numbers, `approval-service.ts:4893`, `:4906` and `stranded-request-inspection.test.ts:123`.** 「[objectstack-ai#16709 item 3]」 became 「[commit 8c7cca1, item 3]」, and likewise for item 2, beside its 「PM ruling, 2026-09-08」, which `8c7cca1ce`'s message records under 「Item 2」. - **Lost referents, 2 lines with no dead site** (every file keeps its line count): `backfill-platform-row-organizations.test.ts:17` 「the one thing this card must not do」 became 「the one thing this sweep must not do」, and `manager-org-screen-parity.contract.test.ts:61` 「the very decision this card is fenced out of」 became 「the very decision this pin is fenced out of」. Each 「this card」 pointed at the number the same comment block opened with, which is now a commit; `b019891cd`'s message says the pin 「PINS the duplication, it does not remove it」. ## The 5 sites left - **Test strings, 5 sites**, left as stages 1 to 6 left theirs: - `describe` / `it` titles: `manager-org-screen-parity.contract.test.ts:232` (`objectstack-ai#11286`), `stranded-request-inspection.test.ts:519` and `:642` (`objectstack-ai#16709`); - a test double's thrown message and an assertion message: `manager-org-screen-parity.contract.test.ts:107` and `:294` (`objectstack-ai#11286`). - There is no operator string, generated header or quoted ruling carrying a dead number in this package. The generated `*.source-hashes.generated.ts` headers already cite `09b4f4e4e` and are untouched. The two verbatim quotations of the 2026-08-15 ruling carry no number and are untouched. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base `575746371` against head. Template literals are therefore read in context. It ran over all 13 touched `.ts` files. - Real run: 36,204 base leaf tokens, **0 files with a token change** (exit 0). - Comment control in `sys-approval-request.object.ts` (「who a row is ABOUT」 to 「whom a row is ABOUT」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `sys-approval-request.object.ts` (`referenceVia: 'object_name',` given a trailing `as const`): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`stranded-request-inspection.test.ts:642`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`6cb56301a334`, `757ad45900ac`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/plugin-approvals` (`.changeset/20596-plugin-approvals-provenance-anchors.md`) is included. Its body is stage 6's, word for word, with the package name changed. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build (a cache miss for this package, so `dist` is this head's source), the rewritten comments reach `dist`: `8c7cca1ce` 4 times and `04d03c3a0` 4 times in each of `dist/index.d.ts` and `index.d.mts`; `04d03c3a0` 4 times, `1cba33f16` twice, and `8c7cca1ce`, `7901b2dd2` and `1408fe385` once each in each of `index.js` and `index.mjs`. Positive controls: the unchanged line 「A step routing to nobody is」, in the same docblock as the shipped rewrite at `approval-service.ts:2295`, is found once in each of the four files, and the unchanged line 「itself stays unwalled (`tenancy.enabled: false`)」 beside the shipped rewrite at `sys-approval-request.object.ts:144` once in each JS file. A never-written negative phrase appears nowhere in `dist`. None of the 14 dead numbers is left anywhere in `dist`. ## Gates (head `708244c2b`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 5 citations across 6 files, and all 5 resolve (`objectstack-ai#8613` twice, `objectstack-ai#8287`, `objectstack-ai#10101`, `objectstack-ai#12069`), each already on the line it replaces. - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `708244c2b` derived 64 commands: all 57 derived at dispatch, plus `check:dispatcher-error-vocabulary`, `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. Each ran with its exit code captured before any pipe, and all 64 exit 0. `--ran`, fed each command with its exit code, reports 64 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/plugin-approvals test`: 51 files pass and 791 tests pass. That is every test file in the package, the 7 touched ones included. - `pnpm --filter @objectstack/plugin-approvals typecheck` exits 0 (`tsc` on `tsconfig.json`, the scripts program, and the test layer on `tsconfig.test.json`, held at its ledger of 8 files, 324 errors and 27 pinned signatures). `--listFiles`: the `tsconfig.json` program holds the 25 non-test files under `src/`, the 6 touched ones included; the `tsconfig.test.json` program holds all 76 files under `src/`, the 51 test files and all 13 touched files included. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 13 touched `.ts` files gives 13 files, 0 errors and 0 warnings. All 13 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 14 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636). In this package there is one `#N-word` spelling, 「objectstack-ai#3266-era」 (`record-reader-visibility.test.ts:342`), and two `#A/#B` spellings, `objectstack-ai#8287/objectstack-ai#8778` (`approval-node.test.ts:462`) and `objectstack-ai#8543/objectstack-ai#8580` (`approval-vocabularies.test.ts:66`): the claim's 3, 1 and 2. `objectstack-ai#3266`, `objectstack-ai#8287`, `objectstack-ai#8543` and `objectstack-ai#8580` answer 200; the second number `objectstack-ai#8778` is dead, so that one line is rewritten here. - **A third spelling the gate cannot see, found by the raw scan.** `NON_CITATION_HEADS` excuses any `#N` after the word 「option」 as an option ordinal, so 「the option objectstack-ai#8710 rejected」 (`approval-service.ts:2329`) was never extracted: a dead number there would pass the diff gate at exit 0 and never enter a census count. It is rewritten here. Across the gate's declared surfaces at the base, the only other `option #N` with three or more digits is `packages/objectql/src/validation/rule-validator.ts:2202` (`option objectstack-ai#14088`), which answers 200. Same family as objectstack-ai#20636; noted for its closeout, not a card of its own. - **A retired key name in this package's prose, not changed here.** `tenancy.organizationField` left the authorable surface in `502f179cc`, and limb 0 of the shared resolver now reads `PLATFORM_STAMP_ORGANIZATION_COLUMNS` in `metadata-core`, keyed by object name. Comments in this package still name the retired key as what limb 0 reads (`sys-approval-request.object.ts:143`, the line above a rewrite; `backfill-platform-row-organizations.ts:35`, `approval-node.test.ts:463`, `approval-service.ts:2707`, `backfill-platform-row-organizations.test.ts:50`), and two test fixtures still declare it on a stub `sys_api_key` (`approval-node.test.ts:467`, `backfill-platform-row-organizations.test.ts:54`), where it is inert because the resolver keys by name. The anchor `7901b2dd2` is right for the key those lines name, and nothing is wrong at runtime. Correcting the prose would reach past the dead citations, and the fixtures are code tokens, so none of it is changed here. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#16709` → `8c7cca1ce`; `objectstack-ai#11081` → `c28e4cfae`; `objectstack-ai#11286` → `b019891cd`; `objectstack-ai#11308` → `5a916c4d4`; `objectstack-ai#11311` → `1272f0a6b`; `objectstack-ai#8707` → `1408fe385`; `objectstack-ai#8863` → `d200b016b`. - **Base.** The branch is on `main` at `575746371`, which is still `main` at 20:56Z (read into a private ref), so there was no merge. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…commits that decided them (objectstack-ai#20729) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the eighth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-analytics/src/**` and nothing else. By the seat's census at the claim (`5899485578`), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 7 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`). That is **76 sites on 76 lines in 22 files, covering 14 numbers**: - 42 census sites (every census site this package has); - 34 sites in test comments, which the census defers. The raw scan found no dead site the gate's grammar cannot see (see Acceptance notes), so there is no third class this time. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **13 distinct shas**. No number in this package has an ADR or ruling record of its own in the repository (a grep of `docs/adr/` for all 14 finds none, and a grep of the rest of `docs/` finds none either), so every anchor is a commit, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (78 lines out, 78 in, over 22 files), so no line citation into these files moves. 2 of those 78 lines hold no dead citation: they are reflow lines, listed under Wordings below. No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces: `objectstack-ai#10861` (5 lines), `objectstack-ai#12776` (3), `objectstack-ai#10413` (2), `objectstack-ai#16750` (2), and `objectstack-ai#10759`, `objectstack-ai#11152`, `objectstack-ai#5716` and the decision-batch ordinal `objectstack-ai#59` once each. Each tracker number among them resolves. Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number is the citation on an added line: the two `PR #N` spellings in scope became their pull request's squash commit, and `objectstack-ai#16750` stays only as the convenience link beside `ed7243d52`, on the line it already stood on. Eight dead sites are left on purpose, all of them test strings (see the list below). One more file: a `patch` changeset for `@objectstack/service-analytics`, because the rewritten docblocks and inline comments ship (see Changeset below). The `AnalyticsResultWithDrill` type and its four sidecar members are not touched: its docblocks carry no dead number (`objectstack-ai#20644`, `objectstack-ai#3214` and `objectstack-ai#1752` all resolve). ## Census: `service-analytics`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/services/service-analytics/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | service-analytics sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `cbaf04c1f`, run 2026-09-29T21:41:53Z to 21:45:05Z | enumerated, 186 pages, frontier objectstack-ai#20721 (newest objectstack-ai#20721 before and after), 18,548 numbers | 1,161 | **42** | 42 | 10 | 10 | | after | head `967d73531`, run 21:55:23Z to 21:58:36Z | enumerated, 186 pages, frontier objectstack-ai#20723 (newest objectstack-ai#20723 before and after), 18,550 numbers | 1,119 | **0** | 0 | 0 | 0 | The before count matches the seat's census at the claim and A1 (42 sites): the two comments PR objectstack-ai#20712 rewrote in `analytics-service.ts` did not move it. The whole-repo drop is 42, exactly this diff's census sites. The `resolves` tally is 32,991 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `967d73531`; the head `82d2b40b2` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `service-analytics/src` (162 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction) and did not report it. The 21 numbers the census never saw, because they stand only in test files or strings here, were read one by one on the issues endpoint: 17 answer 200, and `objectstack-ai#16778`, `objectstack-ai#16860`, `objectstack-ai#16918` and `objectstack-ai#17125` answer 404. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `cbaf04c1f` | 3,514 | **84** | 42 | 34 | 0 | 8 | | after, `967d73531` | 3,438 | **8** | 0 | 0 | 0 | 8 | Its src-comment column equals the census's 42, which is the control on the second instrument. The 3,410 live citations and the 20 cross-repo citations are the same in both readings, and the drop of 76 citations is exactly the rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 3,598 occurrences and 84 dead before, 3,522 and 8 after; its residue equals the gate's residue site for site, and it sees no dead site beyond the gate. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (`merge-base --is-ancestor` exit 0 for each pair). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#11461` | 20/2 | 19/1 | `399ecad58`: a cross-object leaf in one measure's own `filter` (the third producer, lowered onto `aggregations[].filter`) is refused on both ObjectQL doors with `INVALID_FIELD` / 400 naming the measure, folded into the one member view, with insertion order keeping every earlier refusal's message. The last line of its message names `objectstack-ai#11461` as the card it settles. New to the sweep | | `objectstack-ai#17130` | 17/5 | 13/4 | `54b3d1d4a` (PR objectstack-ai#17336): the row-scope resolution refusals carry `READ_SCOPE_COMPILE_FAILED` / 500 through one constructor, so `queryDataset`'s catch re-throws them instead of reading their words, every message byte-unchanged; plus the source-derived wording-collision guard. Named in its diff only (18 added lines carry the tag). New to the sweep | | `objectstack-ai#17124` | 12/8 | 10/2 | `86c505286` (PR objectstack-ai#17593): `explicitDateRangeWindow` is the one reading of `dateRange`'s array arm on all four faces, and an array that is not two string bounds is refused with `ANALYTICS_DATE_RANGE_UNRECOGNIZED` / 400. Named in its diff only (its changeset file is `17124-daterange-array-arm-arity.md`). New to the sweep | | `objectstack-ai#12209` | 10/5 | 10/0 | `017130a09` (PR objectstack-ai#12318): a custom-SQL measure is refused on the ObjectQL aggregate path with `INVALID_FIELD` / 400, keyed on the `EXPRESSION_METRIC_TYPES` partition shared with `NativeSQLStrategy`. Its message records the two failure modes the lines describe (`driver-sql` blaming a `function` key, the in-memory evaluator answering `null` per bucket). Named in its diff only. New to the sweep | | `objectstack-ai#16778` | 5/1 | 4/1 | `357f4992b`: the compile-leg refusal of an aggregate a datetime measure's field type cannot carry, scoped to temporal source fields. The squash commit of the pull request that was `objectstack-ai#16778`; its subject carries the number. New to the sweep | | `objectstack-ai#12940` | 4/2 | 4/0 | `aa16721b6` (PR objectstack-ai#13361): this package's consumer-local `executeAggregate` config mirrors (the plugin options and `AnalyticsServiceConfig`) narrow `aggregations[].method` to `AggregationFunction`, after `objectstack-ai#12776` narrowed the contract. Named in its diff only. New to the sweep | | `objectstack-ai#17015` | 4/2 | 4/0 | `0da638cd9`: the closed `dateRange` preset vocabulary is lowered once and the rest refused, the `[range, range]` fallback is removed from the faces it reached, and the shared conformance kit holds them. The squash commit of the pull request that was `objectstack-ai#17015`. New to the sweep | | `objectstack-ai#16860` | 3/1 | 3/0 | `041d9fdc6`: the object-level read grant is asked at the analytics door, and its bridge to the `security` service resolves an explicit three-way (absent admits; throwing or method-less denies at `error`, finding F3 in its message). The squash commit of the pull request that was `objectstack-ai#16860`. New to the sweep | | `objectstack-ai#12248` | 2/1 | 2/0 | `8425c17cc`: the five ruled engine members, `getDriverForObject?` and `resolveEffectiveDatasource` among them, adopted onto `IDataEngine`, and `getObject` typed. Its subject names it. Stage 5's and the spec stage's anchor | | `objectstack-ai#16685` | 2/2 | 2/0 | `ed7243d52` (PR objectstack-ai#16750): `boolean` / `toggle` accepted for `sum` / `avg` / `min` / `max` in the aggregate × field-type table, holding maintainer ruling `objectstack-ai#11152`. Its subject names it. The spec stage's anchor | | `objectstack-ai#17125` | 2/2 | 2/0 | `5d12b16e7`: the row-scope bridge tells an absent security service from a broken one, so a broken one refuses the query. The squash commit of the pull request that was `objectstack-ai#17125` (404 on the pulls endpoint too). New to the sweep | | `objectstack-ai#16918` | 1/1 | 1/0 | `5d12b16e7`: the same commit. Its changeset's headline names `objectstack-ai#16918` as the card it answers, and its diff writes the line (`admission-bridge-resolution.test.ts:120`) | | `objectstack-ai#6123` | 1/1 | 1/0 | `59d1933f9`: `err.code` lands at `error.code`, not `error.details.code`; the commit that wrote this very line. The `runtime` stage's anchor | | `objectstack-ai#13279` | 1/1 | 1/0 | `6a180e42d`: permission-store read failures fail loud, and the same commit renames `metadata/src/utils/schema-sync-errors.ts` to `packages/types/src/driver-error-classification.ts`, the move the line describes. The anchor of stages 2, 5 and 6, and of the `types`, `rest` and `runtime` stages | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 13), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 13; control leg: stage 1's landing `422db788a` exit 0; the history is complete, `--is-shallow-repository` false, 15,135 commits). Each of the 14 numbers answers 404 on the issues endpoint, read one by one; `objectstack-ai#16778`, `objectstack-ai#16860`, `objectstack-ai#17015` and `objectstack-ai#17125` answer 404 on the pulls endpoint too. ## Wordings to check - **Bracket tags.** `[#N]` became `[commit SHA]`, as in stage 7; `[objectstack-ai#10861 / objectstack-ai#11461]` and `[objectstack-ai#10861, objectstack-ai#11461]` keep the live `objectstack-ai#10861` beside the new sha. - **The boolean rows, `measure-result-type.ts:115-116` and `aggregate-datetime-measure-refusal.test.ts:65-66`.** 「objectstack-ai#16685 ruled A, landed as objectstack-ai#16750」 and 「objectstack-ai#16685 was ruled A and objectstack-ai#16750 added」 became 「commit ed7243d (objectstack-ai#16750) added those rows」 and 「commit ed7243d (objectstack-ai#16750) added」. 「ruled A」 named an option on the dead card; `ed7243d52`'s message records the decision itself. Line 116 of the first file and line 66 of the second are the 2 reflow lines: each keeps the `objectstack-ai#16750` it already carried. - **PR numbers, `read-scope-resolution-envelope.test.ts:25` and `refusal-wording-collision.test.ts:21`.** 「PR objectstack-ai#17125's refusal」 became 「Commit 5d12b16's refusal」, the pull request's squash commit. - **`read-scope-refusal.ts:29`.** 「objectstack-ai#17130 exists to remove it」 became 「commit 54b3d1d was made to remove it」, the form stage 6 used. - **`refusal-wording-collision.test.ts:49`.** 「the exact move objectstack-ai#17130 forbids」 became 「the exact move commit 54b3d1d ruled out」; its message says the fix is the declaration, not a luckier string. - **`read-scope-resolution-envelope.test.ts:161`.** The verb after the number moved from present to past tense with the sha. - **`measure-expression-both-strategies.test.ts:45` and `:166`.** 「deleting the objectstack-ai#12209 arm in」 became 「deleting the arm commit 017130a added in」, and 「every objectstack-ai#12209 refusal」 became 「every custom-SQL refusal (commit 017130a)」. - **`dataset-executor.ts:609`.** 「objectstack-ai#17015's kit」 became 「commit 0da638c's kit」, the conformance kit that commit built. - **`plugin.ts:116`.** 「and in objectstack-ai#12209:」 became 「and in commit 017130a:」, whose message records the two ways the engine failed. - **`analytics-service.ts:238`.** 「objectstack-ai#13279 moved it there」 became 「commit 6a180e4 moved it there」; that commit's diff is the rename. ## The 8 sites left - **Test strings, 8 sites**, left as stages 1 to 7 left theirs, all `describe` / `it` titles: - `crossobject-conjunct-refusal.test.ts:589` (`objectstack-ai#11461`); - `aggregate-nontemporal-measure-refusal.test.ts:243` (`objectstack-ai#16778`); - `date-range-array-arm-arity.test.ts:213` and `:294` (`objectstack-ai#17124`); - `read-scope-resolution-envelope.test.ts:155`, `:199` and `:226`, and `refusal-wording-collision.test.ts:336` (`objectstack-ai#17130`). - There is no operator string, generated file or quoted ruling carrying a dead number in this package. It has no generated file at all. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base `cbaf04c1f` against head. Template literals are therefore read in context. It ran over all 22 touched `.ts` files. - Real run: 26,705 base leaf tokens, **0 files with a token change** (exit 0). - Comment control in `plugin.ts` (「refusal buys is in」 to 「refusal earns is in」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `plugin.ts` (`field: a.field,` given `as string`): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`date-range-array-arm-arity.test.ts:213`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`ad3dc9fff4d3`, `a606ffbb6ead`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-analytics` (`.changeset/20596-service-analytics-provenance-anchors.md`) is included. Its body is stage 7's, word for word, with the package name changed. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build (a cache miss for this package, so `dist` is this head's source), the rewritten comments reach `dist`: `399ecad58` 6 times in each of `dist/index.js`, `index.cjs`, `index.d.ts` and `index.d.cts`; `86c505286` twice in each JS file and once in each declaration file; `54b3d1d4a` once in all four; `aa16721b6` once in each JS file and twice in each declaration file; `017130a09` once in each JS file. Positive controls: the unchanged line 「none of the coverage: a compiled measure's own」, in the same docblock as the shipped rewrite at `objectql-strategy.ts:744`, is found once in each of the four files, and the unchanged line 「back into line. Widening it here again would not be a local matter」 beside the shipped rewrite at `analytics-service.ts:559` once in each declaration file. A never-written negative phrase appears nowhere in `dist`. None of the 14 dead numbers is left anywhere in `dist`. ## Gates (head `82d2b40b2`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 11 citations across 10 files; 10 resolve and 1 resolves as a pull request (`objectstack-ai#16750`, the convenience link that already stood on its line). - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `82d2b40b2` derived 62 commands: all 56 derived at dispatch, plus `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. Each ran with its exit code captured before any pipe, and all 62 exit 0. `--ran`, fed each command with its exit code, reports 62 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/service-analytics test`: 137 files pass and 3,216 tests pass. That is every test file in the package, the 12 touched ones included. - `pnpm --filter @objectstack/service-analytics typecheck` exits 0 (`tsc --noEmit` on `tsconfig.json`). `--listFiles`: the program holds all 162 files under `src/`, the 137 test files and all 22 touched files included. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 22 touched `.ts` files gives 22 files, 0 errors and 0 warnings. All 22 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 23 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word 「option」. In this package: - `#N-word`: 8 lines by a plain grep, and 7 once a hyphen before the `#` is excluded too, which is the claim's 7. The eighth is 「pre-objectstack-ai#10413-phase-2」 (`execution-context-bridge.test.ts:223`). The numbers, `objectstack-ai#10413`, `objectstack-ai#5298`, `objectstack-ai#13570` and `objectstack-ai#13640`, all resolve. - `#A/#B`: 29 lines, the claim's 29, over 28 distinct numbers. All resolve; `objectstack-ai#2149`, which the census never judged, was read on its own. - `option #N`: none. So nothing here needed a rewrite beyond the gate, and the raw scan agrees. - **「This card」 phrases are left.** 113 lines in 39 files of this package speak of 「this card」, 「that card」 or 「the card」. They carry no number, neither instrument sees them, and most sit in blocks whose numbers still resolve. Stage 7 rewrote two such lines as lost referents; here none is changed, because the phrase runs through the whole package and rewriting a subset would be arbitrary. - **Prose that names `queryDataset`'s catch, not changed.** Nine comment lines say `queryDataset`'s catch. Since `10c36cc43` that catch sits in the private `answerDataset`, whose docblock calls it the body of `queryDataset`, so the lines still hold at the level of the public method. This is not a dead citation, so it is outside this stage. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#11461` → `399ecad58`; `objectstack-ai#17130` → `54b3d1d4a`; `objectstack-ai#17124` → `86c505286`; `objectstack-ai#12209` → `017130a09`; `objectstack-ai#16778` → `357f4992b`; `objectstack-ai#12940` → `aa16721b6`; `objectstack-ai#17015` → `0da638cd9`; `objectstack-ai#16860` → `041d9fdc6`; `objectstack-ai#17125` and `objectstack-ai#16918` → `5d12b16e7`. - **Base.** The branch is on `main` at `cbaf04c1f`. `main` has since moved four commits (`3711e0b76`, `61455de27`, `6afccda5a`, `671d4c164`). They touch `packages/spec`, `packages/metadata/package.json`, `pnpm-lock.yaml`, docs and changesets, and no file under `service-analytics` or in this diff, so no merge was taken; the merge queue rebuilds on the merged generation. One of them, `671d4c164`, declares the four drill-through sidecars on `AnalyticsResult` in the spec. This diff leaves the local `AnalyticsResultWithDrill` untouched, as the claim requires. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ts that decided them (objectstack-ai#20737) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the ninth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/plugins/plugin-audit/src/**` and nothing else. By the seat's census at the claim (`5900808881`), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 8 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`). That is **56 sites on 55 lines in 16 files, covering 16 numbers**: - 23 census sites (every census site this package has); - 32 sites in test comments, which the census defers; - 1 site the gate's grammar cannot see: the slash-joined second number in `objectstack-ai#9719/objectstack-ai#9798` (`comment-access-hooks.ts:35`). Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **15 distinct shas**. No number in this package has an ADR or ruling record of its own in the repository (a grep of `docs/adr/` for all 16 finds none; the rest of `docs/` cites `objectstack-ai#11507` and `objectstack-ai#11374` only as evidence, in an audit table and a QA checklist), so every anchor is a commit, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (56 lines out, 56 in, over 16 files), so no line citation into these files moves. 1 of those 56 lines holds no dead citation: it is a reflow line, listed under Wordings below. No code token moves (see the guard below). **No citation number is added.** Every tracker number on an added line was already on the line it replaces: `objectstack-ai#10101` (3 lines), `objectstack-ai#8287` (3), `objectstack-ai#5928` (2), `objectstack-ai#9974` (2), `objectstack-ai#4630` (2), and `objectstack-ai#8144`, `objectstack-ai#9719`, `objectstack-ai#12069` and `objectstack-ai#19054` once each. Each resolves. Over the whole diff, added minus removed is 0 for every number, and no number is new to the diff. No PR number is the citation on an added line: the two `PR #N` spellings in scope became their pull request's squash commit. 23 dead sites are left on purpose, all of them string literals (see the list below). One more file: a `patch` changeset for `@objectstack/plugin-audit`, because some of the rewritten docblocks and inline comments ship (see Changeset below). ## Census: `plugin-audit`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/plugins/plugin-audit/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | plugin-audit sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `d2820876f`, run 2026-09-29T23:11:55Z to 23:15:11Z | enumerated, 186 pages, frontier objectstack-ai#20735 (newest objectstack-ai#20735 before and after), 18,562 numbers | 1,110 | **23** | 22 | 6 | 12 | | after | head `a9a4ea478`, run 23:26:11Z to 23:29:20Z | enumerated, 186 pages, frontier objectstack-ai#20735 (newest objectstack-ai#20735 before and after), 18,562 numbers | 1,087 | **0** | 0 | 0 | 0 | The before count matches the seat's census at the claim and A1 (23 sites). The whole-repo drop is 23, exactly this diff's census sites. The `resolves` tally is 32,995 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `a9a4ea478`; the head `d6e67afa5` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `plugin-audit/src` (45 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction, 37,084 citations over 2,613 files) and did not report it. The 10 numbers the census never saw, because they stand only in test files or strings here, were read one by one on the issues endpoint: 7 answer 200 (`objectstack-ai#602`, `objectstack-ai#1532`, `objectstack-ai#4186`, `objectstack-ai#7291`, `objectstack-ai#7333`, `objectstack-ai#16312`, `objectstack-ai#20494`), and `objectstack-ai#8852`, `objectstack-ai#12143` and `objectstack-ai#12147` answer 404, on the pulls endpoint too. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `d2820876f` | 655 | **77** | 23 | 32 | 5 | 17 | | after, `a9a4ea478` | 600 | **22** | 0 | 0 | 5 | 17 | Its src-comment column equals the census's 23, which is the control on the second instrument. The 572 live citations and the 6 cross-repo citations are the same in both readings, and the drop of 55 citations is exactly the rewritten sites the gate's grammar sees. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 672 occurrences and 79 dead before, 616 and 23 after. Beyond the gate's grammar it sees 2 dead sites before (the `objectstack-ai#9719/objectstack-ai#9798` comment, rewritten, and the `[objectstack-ai#8203/objectstack-ai#11507]` test title, left) and 1 after (that title). Its only unjudged tokens are `objectui#10520`, `cloud#340`, `cloud#1395` and the decision-batch ordinal `objectstack-ai#153`. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (`merge-base --is-ancestor` exit 0 for all 56 line and anchor pairs). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#11507` | 26/8 | 10/16 | `88b9d749a`: `sys_activity.type` is declared an open, author-extensible vocabulary whose options are the built-in set, per the maintainer ruling of 2026-08-24, direction 4. Its body names `objectstack-ai#11507` twice. The spec stages' anchor | | `objectstack-ai#8707` | 12/2 | 9/3 | `1408fe385`: an audit row is stamped from the record's own organization, not the actor's, applying the maintainer's ruling on `objectstack-ai#8287`; the precedence flips to `recordOrgId ?? sess.tenantId`, and the organization column is resolved from the schema (`resolveRecordOrganizationField`, first written in this file). Its subject names it. Stage 7's anchor | | `objectstack-ai#9798` | 8/2 | 7/1 | `c7655d472` (PR objectstack-ai#9993): the `sys_comment` access-hook registration declares the whole-operation dispatch `objectstack-ai#9719` built, so the `objectstack-ai#4630` unscoped multi-delete refusal reaches the handler through the wired engine; the update half is split out. Its body ends with the closing line for `objectstack-ai#9798`. The `lint` stage's anchor | | `objectstack-ai#16829` | 7/3 | 6/1 | `8d4690b8f`: the read-audit ledger write declares `preserveAudit`, so a record-view row keeps the VIEW instant; `isSystem` is kept for the readonly strip, and the new integration pin runs the real stamp hook. Its body ends with the closing line for `objectstack-ai#16829`. New to the sweep | | `objectstack-ai#6575` | 4/2 | 4/0 | `69787f07b`: the hook registration surface gains `excludeObjects` ("global except these objects"), refusing `'*'` and blank members on it. The squash commit of the pull request that was `objectstack-ai#6575` (404 on the pulls endpoint too); `objectstack-ai#5928`, the card it answers, stays beside it. New to the sweep | | `objectstack-ai#11374` | 4/3 | 3/1 | `f64668d3c`, the squash commit of `objectstack-ai#12143`, for the two object comments: sourced bounds on the keyed text columns `sys_activity.record_id` and `sys_audit_log.record_id` (255, the physical `id` column), route A. `3954fb7df`, for the test's statement of the rule: the route A ruling that keyed identity columns declare a sourced `maxLength`; its subject names `objectstack-ai#11374 route A`. Both are stage 4's anchors for the sibling lines in `plugin-security` | | `objectstack-ai#10091` | 3/3 | 3/0 | `da891e0ef` (PR objectstack-ai#10169): `sys_attachment`'s `beforeUpdate` gate, uploader or parent editor, with the attach rule on the NEW parent when a row is re-pointed. Its body names `objectstack-ai#10091`. Stage 6's anchor | | `objectstack-ai#14927` | 3/2 | 3/0 | `ab489388b` (PR objectstack-ai#17450): a lost audit row is reported once per cause, keyed on the error `code`, and the datasource remedy prints only for the missing-table cause; its message records that the measured `ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED` refusal had sent its operator to a working datasource. It names `objectstack-ai#14927` in its diff only (the 3 lines it wrote). New to the sweep | | `objectstack-ai#8778` | 3/1 | 2/1 | `7901b2dd2` (PR objectstack-ai#8905): the stamp-only `tenancy.organizationField`, option A per the maintainer ruling on `objectstack-ai#8778`. Its subject names it. The anchor of stages 4, 6 and 7 | | `objectstack-ai#6523` | 2/2 | 2/0 | `aa4b90d9a` (PR objectstack-ai#7068): enforcement contracts take the full `ExecutionContext`. Its subject names `objectstack-ai#6523` | | `objectstack-ai#6206` | 2/2 | 2/0 | `aa4b90d9a`: the same commit, whose body applies "the objectstack-ai#6206 ruling default (converge on the full envelope, keep no per-site subset contracts)", written as the full-envelope ruling, the form of stages 2, 6 and 7 | | `objectstack-ai#8852` | 1/1 | 1/0 | `51bb277ef`: the `sys_activity.type` writer census; its message records the objectui mirror as unguarded in both directions, filed as `objectstack-ai#8852`, and not asserted here because this package cannot import objectui. The commit that wrote the line. New to the sweep | | `objectstack-ai#11674` | 1/1 | 1/0 | `1cba33f16` (PR objectstack-ai#11961): the load-time warning and the ordering constraint documented at the four pointer-pair sites. Its subject names it; blame puts the line in it. Stage 7's anchor | | `objectstack-ai#12147` | 1/1 | 1/0 | `945e91a13`: the class-level keyed-text-bounds gate over every `*.object.ts`, retiring the per-package rule this file carried. It names `objectstack-ai#12147` in its diff only. Stage 4's anchor for the sibling file | | `objectstack-ai#12143` | 1/1 | 1/0 | `f64668d3c`: the squash commit of the pull request that was `objectstack-ai#12143` (404 on both endpoints), where the dependency-graph measurement was made. Stage 4's anchor | | `objectstack-ai#11671` | 1/1 | 1/0 | `09b4f4e4e` (PR objectstack-ai#12557): records which source revision a generated translation leaf was filled from. The anchor the identical `translations/index.ts` line already carries in five packages on `main` | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 15), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 15; control leg: stage 1's landing `422db788a` exit 0; the history is complete, `--is-shallow-repository` false, 15,143 commits). Each of the 16 numbers answers 404 on the issues endpoint. ## Wordings to check - **Bracket tags.** `[#N]` became `[commit SHA]`; `[#N route A]` became `[commit f64668d, route A]`, the form stage 4 gave the sibling lines; `[objectstack-ai#8707 / objectstack-ai#10101]` and `[objectstack-ai#8144 / objectstack-ai#8707 / objectstack-ai#10101]` keep the live numbers beside the sha. - **`sys-activity.object.ts:59-60`.** 「Maintainer ruling 2026-08-24 on / objectstack-ai#11507 (direction 4 of the four that card framed)」 became 「Maintainer ruling 2026-08-24, / executed by commit 88b9d74 (direction 4 of the four weighed)」. Line 59 is the one reflow line: only its last word changed, and it carries no number. 「that card」 would have lost its referent. - **「re-open objectstack-ai#11507」**, `sys-activity.object.ts:92` and `activity-type-vocabulary-enforcement.test.ts:332`, became 「re-open the ruling (commit 88b9d74)」: a card that answers 404 cannot be re-opened, and the instruction is about the decision. - **`sys-activity-type-open-vocabulary.test.ts:14`**, the attribution above a verbatim maintainer ruling: 「on objectstack-ai#11507 (direction 4 of the four the card framed)」 became 「on the card behind commit 88b9d74 (direction 4 of the four it framed)」, so line 15's 「Recorded on the card as:」 keeps its referent. Line 15, which carries the ruling 「四维分析一致的,接手你的建议。」, and the quoted block under it are untouched. - **Headings.** 「what the ruling on objectstack-ai#11507 changed」 and 「the objectstack-ai#11507 ruling」 became 「the open-vocabulary ruling (commit 88b9d74)」; 「objectstack-ai#11507 — the declaration」 became 「Commit 88b9d74 — the declaration」. - **PR numbers.** 「objectstack-ai#5928 / PR objectstack-ai#6575」 (`audit-writers.ts:193`) and 「(objectstack-ai#5928, PR objectstack-ai#6575)」 (`audit-hook-object-scope.test.ts:19`) became `commit 69787f0` beside the kept `objectstack-ai#5928`; 「measured on PR objectstack-ai#12143」 (`plugin-keyed-text-bounds.test.ts:21`) became 「measured on commit f64668d」. - **The `objectstack-ai#8707` ruling phrases**, `audit-writers.test.ts:1882` and `:1922`. 「the ORDER the objectstack-ai#8707 ruling set」 became 「the ORDER commit 1408fe3 set」, and 「objectstack-ai#8707's ruling reasons about」 became 「commit 1408fe3 reasons about」: the ruling was the maintainer's on `objectstack-ai#8287`, which stays on those lines, and `1408fe385`'s message carries the reasoning. `audit-writers.ts:1402` 「because objectstack-ai#8707 reordered」 became 「because commit 1408fe3 reordered」, the flip its message states. - **`objectstack-ai#14927`, three lines.** 「the cause measured on objectstack-ai#14927」 became 「the cause commit ab48938 records」 (`audit-writers.ts:789`, `audit-writers.test.ts:1211`), and 「The measured objectstack-ai#14927 misdirection」 became 「The misdirection commit ab48938 records」 (`audit-writers.test.ts:1370`). - **`sys-activity-type-vocabulary.test.ts:91`.** 「Filed as objectstack-ai#8852;」 became 「Commit 51bb277 recorded it;」. - **`comment-access-hooks.test.ts:404-405`.** 「the objectstack-ai#6523 contract's unit is the envelope / and objectstack-ai#6206 forbids」 became 「the unit of commit aa4b90d's contract is the envelope / and the full-envelope ruling forbids」; `comment-access-hooks.ts:222` 「(objectstack-ai#6523 / the objectstack-ai#6206 ruling)」 became 「(commit aa4b90d / the full-envelope ruling)」. - **`audit-writers.test.ts:1648`**, a section rule: the trailing rule was shortened from 10 characters to 2 so the line stays near its old width. `:1653` 「That day is objectstack-ai#8778」 became 「That day came with commit 7901b2d」. - **`read-audit.test.ts:43`.** 「precisely how / objectstack-ai#16829 shipped」 became 「precisely how / the defect fixed by commit 8d4690b shipped」. - **`comment-access-hooks.ts:35`**, the gate-invisible site: 「(objectstack-ai#9719/objectstack-ai#9798 built」 became 「(objectstack-ai#9719/commit c7655d4 built」. ## The 23 sites left - **Source strings, 5 sites**, all `objectstack-ai#11507`: the `sys_activity.type` field's `description` (`objects/sys-activity.object.ts:121`) and its four generated copies (`translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts:125`). They are runtime strings, all five are held by the shrink-only `doc-authoring-prose-id` baseline, and the generated files are left as A5 says. They ship in `dist` (see Changeset). - **Test strings, 18 sites**, left as stages 1 to 8 left theirs: - `describe` / `it` titles: `activity-type-vocabulary-enforcement.test.ts:315` (the gate-invisible `[objectstack-ai#8203/objectstack-ai#11507]`), `sys-activity-type-open-vocabulary.test.ts:70` (`objectstack-ai#11507`), `audit-writers.test.ts:1420`, `:1659` (two sites, `objectstack-ai#8707` and `objectstack-ai#8778`) and `:1873` (`objectstack-ai#8707`), `comment-access-hooks.test.ts:690` (`objectstack-ai#9798`), `plugin-keyed-text-bounds.test.ts:90` (`objectstack-ai#11374`), `read-audit-view-instant-preservation.integration.test.ts:121` (`objectstack-ai#16829`); - assertion and hint messages, all `objectstack-ai#11507`: `activity-type-vocabulary-enforcement.test.ts:249`, `:352`, `:355`, `:378`, `:380`, and `sys-activity-type-open-vocabulary.test.ts:83`, `:90`, `:110`, `:152`. - No quoted maintainer ruling in this package carries a dead number. The package's generated `*.source-hashes.generated.ts` headers carry none either (PR objectstack-ai#20656 fixed their producer). ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base `d2820876f` against head. Template literals are therefore read in context. It ran over all 16 touched `.ts` files. - Real run: 19,445 base leaf tokens, **0 files with a token change** (exit 0). - Comment control in `audit-writers.ts` (「the cause commit ab48938 records」 to 「… recorded」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `audit-writers.ts` (`createRecordOrganizationResolver(engine)` given `as any`): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`audit-writers.test.ts:1873`, `objectstack-ai#8707` to `objectstack-ai#8708`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`2dbd2059e8f5`, `8ec28790da22`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/plugin-audit` (`.changeset/20596-plugin-audit-provenance-anchors.md`) is included. Its body is stage 8's, word for word, with the package name changed. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build, part of the rewritten prose reaches `dist`: `c7655d472` twice in each of `dist/index.js` and `index.mjs` and once in each of `index.d.ts` and `index.d.mts` (the `CommentAccessEngine` option docblock is on an exported interface); `88b9d749a` and `f64668d3c` twice, and `1cba33f16` and `8d4690b8f` once, in each JS file (the object-definition comments and a `read-audit.ts` comment). The comments in `audit-writers.ts` and `translations/index.ts` do not reach `dist` (0 for each of their anchors). Positive controls: the unchanged line 「below carries into the contract; this comment carries the reasoning.」, in the same docblock as the shipped rewrite at `sys-activity.object.ts:60`, is found once in each JS file, and the unchanged line beside the shipped rewrite at `comment-access-hooks.ts:77` once in each declaration file. A never-written negative phrase appears nowhere in `dist`. Of the 16 dead numbers, only `objectstack-ai#11507` is left in `dist`, 5 times in each JS file: the kept `description` string and its four generated copies. ## Gates (head `d6e67afa5`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test, 114 cases, 8 batteries) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 11 citations across 6 files, and all 11 resolve (they are the live numbers that already stood on the rewritten lines). - **Doc authoring:** `pnpm check:doc-authoring` exits 0; the sibling-package prose-id baseline holds (808 pinned sites, no growth), which includes the five kept `objectstack-ai#11507` strings. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `d6e67afa5` derived 64 commands: all 57 derived at dispatch, plus `check:dispatcher-error-vocabulary`, `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. Each ran with its exit code captured before any pipe, and all 64 exit 0. `--ran`, fed each command with its exit code, reports 64 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - The derivation warns that its tree is 3 commits behind `origin/main` and that one input, `scripts/engine-double-contract.pinned.json`, changed there: `main` added one pinned row for `packages/objectql/src/protocol-packaged-dashboard-base.test.ts`, a file outside this diff. The family is in the 64 either way and exits 0 on this tree. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/plugin-audit test`: 26 files pass and 366 tests pass. `vitest list --filesOnly` names 26 files, all the tracked test files, the 10 touched ones included. - `pnpm --filter @objectstack/plugin-audit typecheck` exits 0. `tsc --listFiles`: `tsconfig.json` holds the 6 touched source files (19 `src` files; it excludes tests), and `tsconfig.test.json`, which the script's `check:test-typecheck` step compiles, holds all 45 files under `src/`, all 16 touched files included. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 16 touched `.ts` files gives 16 files, 0 errors and 0 warnings. All 16 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 17 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word 「option」. In this package: - `#N-word`: none. - `#A/#B`: 11 lines, the claim's 11, over 14 distinct numbers. Two second numbers are dead: `objectstack-ai#9798` in `comment-access-hooks.ts:35`, rewritten, and `objectstack-ai#11507` in the test title `activity-type-vocabulary-enforcement.test.ts:315`, left as a string. The other 12 numbers resolve. - `option #N`: none. The raw scan agrees: nothing dead beyond the gate is left outside a kept string. - **The kept `description` string is a runtime string with a dead number.** `sys_activity.type`'s `description` ships to the metadata API, the i18n bundles and `dist`, and ends 「(maintainer ruling 2026-08-24, objectstack-ai#11507)」. It and its four generated copies are held by the `doc-authoring-prose-id` baseline, so they belong to the runtime-string lane (form D), not to this stage, as stages 1, 2 and 4 left theirs. - **「This card」 phrases are left.** 46 lines in 21 files of this package speak of 「this card」, 「that card」 or 「the card」. They carry no number and neither instrument sees them. Two were rewritten here because the rewrite on their own line removed their referent (`sys-activity.object.ts:60`, `sys-activity-type-open-vocabulary.test.ts:14`); the rest are unchanged, as in stage 8. - **Dead `objectstack-ai#11507` and `objectstack-ai#11374` outside the census surface.** `docs/qa/platform-checklist/areas/records-forms.json` (4 lines) and `docs/audits/gate-census-2026-09.md` (1 line) cite them as evidence. `docs/` is outside this stage's surface; noted for objectstack-ai#20556, the carrier of dead citations outside `packages/spec/src`. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#16829` → `8d4690b8f`; `objectstack-ai#6575` → `69787f07b`; `objectstack-ai#14927` → `ab489388b`; `objectstack-ai#8852` → `51bb277ef`; `objectstack-ai#9798` → `c7655d472`; `objectstack-ai#11507` → `88b9d749a`. - **Base.** The branch is on `main` at `d2820876f`. `main` has since moved three commits (`f05919b82`, `99786f930`, `1940afdaf`). They touch `packages/spec`, `packages/metadata-protocol`, one new `packages/objectql` test file, a design doc, three changesets and `scripts/engine-double-contract.pinned.json` (one added row for that test file), and no file under `plugin-audit`, `scripts/check-issue-citations.mjs` or `.changeset/config.json`, so no merge was taken; the merge queue rebuilds on the merged generation. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…mmits that decided them (objectstack-ai#20742) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the tenth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-package/src/**` and nothing else. By the seat's census at the claim (`5901757839`), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 9 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR objectstack-ai#20737 as `4dfff176b`). That is **18 sites on 17 lines in 5 files, covering 5 numbers**: - 13 census sites (every census site this package has); - 5 sites in test comments, which the census defers; - no site the gate's grammar cannot see (the package has none, see Acceptance notes). Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **4 distinct shas**. No number in this package has an ADR or ruling record of its own in the repository (a grep of `docs/adr/` and `scripts/adr-anchors/` for all 5 finds none, and nothing else under `docs/` names them), so every anchor is a commit, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (17 lines out, 17 in, over 5 files), so no line citation into these files moves. Every one of the 17 changed lines carried a dead citation; there is no reflow line. No code token moves (see the guard below). **No citation number is added.** The one tracker number on an added line, `objectstack-ai#10677`, was already on the line it replaces (`index.ts:234`) and resolves. Over the whole diff, added minus removed is 0 for `objectstack-ai#10677` and negative for the five dead numbers, and no number is new to the diff. No PR number is the citation on an added line: the three `PR #N` spellings in scope became their pull request's squash commit. 4 dead sites are left on purpose, all of them `describe` titles (see the list below). One more file: a `patch` changeset for `@objectstack/service-package`, because one rewritten docblock ships (see Changeset below). ## Census: `service-package`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/services/service-package/`. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | service-package sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `4dfff176b`, run 2026-09-30T00:41:15Z to 00:44:24Z | enumerated, 186 pages, frontier objectstack-ai#20741 (newest objectstack-ai#20741 before and after), 18,568 numbers | 1,082 | **13** | 12 | 1 | 4 | | after | head `34ba921e6`, run 00:49:33Z to 00:52:49Z | enumerated, 186 pages, frontier objectstack-ai#20741 (newest objectstack-ai#20741 before and after), 18,568 numbers | 1,069 | **0** | 0 | 0 | 0 | The before count matches the seat's census and A1 (13 sites). The whole-repo drop is 13, exactly this diff's census sites. The `resolves` tally is 33,003 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `34ba921e6`; the head `ffd2f1ed2` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `service-package/src` (6 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction, 37,065 rows) and did not report it. The one number the census never saw, because it stands only in test files here, was read on its own: `objectstack-ai#16650` answers 404 on the issues endpoint and on the pulls endpoint. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `4dfff176b` | 82 | **22** | 13 | 5 | 0 | 4 | | after, `34ba921e6` | 64 | **4** | 0 | 0 | 0 | 4 | Its src-comment column equals the census's 13, which is the control on the second instrument. The 60 live citations are the same in both readings (no cross-repo citation stands in this package), and the drop of 18 citations is exactly the rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 82 occurrences and 22 dead before, 64 and 4 after: the same as the gate's grammar, so nothing here sits beyond it, and it has no unjudged token. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (`merge-base --is-ancestor` exit 0 for all 17 line and anchor pairs). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#10965` | 17/3 | 13/4 | `ab47f6974` (PR objectstack-ai#11064): `get()` and `list()` refuse a storage seam that accepted the query and returned no result set, with a declared ADR-0112 envelope (`SERVICE_UNAVAILABLE` / 503), and the skipped boot rehydration is logged at warn; a seam that answers with zero rows is unchanged. Its body says `Part of objectstack-ai#10965` three times, and it is the only commit that wrote the seam guard (`git log -S packageSeamUnreadableError`). The `runtime` stage's anchor for the same number | | `objectstack-ai#10788` | 1/1 | 1/0 | `3a7ec2d3b`: `os migrate duplicates` holds a raw-SQL seam that cannot answer to be absent, not empty. The squash commit of the pull request that was `objectstack-ai#10788` (404 on the pulls endpoint too); `objectstack-ai#10677`, the card it answers, stays beside it. New to the sweep | | `objectstack-ai#10789` | 1/1 | 1/0 | `38bc74ed1`: `backfillSeedTenancy`'s read probes hold a seam that cannot answer to be absent, not empty. Its subject names `objectstack-ai#10789`. The `runtime` stage's anchor for the same number | | `objectstack-ai#10964` | 1/1 | 1/0 | `38bc74ed1`: the same commit, the squash commit of the pull request that was `objectstack-ai#10964` (404 on the pulls endpoint too), so the pair `objectstack-ai#10789 / PR objectstack-ai#10964` became one sha | | `objectstack-ai#16650` | 2/2 | 2/0 | `001a83b04`: `SqlDriver.execute()` declares a backend refusal as `DATABASE_ERROR` / 500. The squash commit of the pull request that was `objectstack-ai#16650`; its review round (「pin the package-door code flip」) wrote the two `[objectstack-ai#16019]` blocks whose closing sentence these lines are. The `rest` stage's anchor for the same sentence in `package-door-16019-raw-statement-fault-code.test.ts` | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 4), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 4; control leg: stage 1's landing `422db788a` exit 0; the history is complete, `--is-shallow-repository` false, 15,149 commits). Each of the 5 numbers answers 404 on the issues endpoint and on the pulls endpoint. ## Wordings to check - **Bracket tags.** `[objectstack-ai#10965]` became `[commit ab47f69]` on 9 lines of `index.ts` (`:208`, `:286`, `:304`, `:327`, `:451`, `:476`, `:502`, `:517`, `:626`). - **`index.ts:223`**, a section heading: 「(objectstack-ai#10965)」 became 「(commit ab47f69)」, and its trailing rule was shortened from 11 characters to 2 so the line stays near its old width. - **`index.ts:234-235`**, the two siblings of the seam guard: 「(objectstack-ai#10677 / PR objectstack-ai#10788 for / `os migrate duplicates`, objectstack-ai#10789 / PR objectstack-ai#10964 for `backfillSeedTenancy`)」 became 「(objectstack-ai#10677 / commit 3a7ec2d for / `os migrate duplicates`, commit 38bc74e for `backfillSeedTenancy`)」. The live `objectstack-ai#10677` stays beside its fix; the dead issue and its dead pull request became their one squash commit. - **`mysql2-tuple.test.ts:26` and `:196`.** 「objectstack-ai#10965's guard」 and 「(objectstack-ai#10965's leg」 became 「commit ab47f69's guard」 and 「(commit ab47f69's leg」. - **`null-seam.test.ts:4`**, the file's title line: 「objectstack-ai#10965 — `get()` / `list()` answered over a driver they never queried.」 became 「The card behind commit ab47f69 — …」, so line 8's 「The card established the conflation by READING」 keeps its referent. - **`delete-driver-fault.test.ts:319` and `publish-driver-fault.test.ts:357`.** 「The reviewer of PR objectstack-ai#16650 required the flip」 became 「The reviewer of commit 001a83b required the flip」, the `rest` stage's form for the same sentence. ## The 4 sites left - **Test strings, 4 sites**, all `objectstack-ai#10965`, all `describe` titles in `null-seam.test.ts` (`:140`, `:184`, `:229`, `:284`), left as stages 1 to 9 left theirs. - No source string, operator log string, assertion message, quoted maintainer ruling or generated file in this package carries a dead number. ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes (a `forEachChild` walk, so comments are trivia and JSDoc nodes are never visited), base `4dfff176b` against head. String and template literals are therefore read in full. It ran over all 5 touched `.ts` files. - Real run: 3,323 base leaf tokens, **0 files with a token change** (exit 0). - Comment control in `index.ts` (「Is this the seam refusal above?」 to 「… named above?」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `index.ts` (`isResultSet(result)` given `as any` in `get()`): DIFFER (exit 1). - Positive control, one digit changed inside a kept test title (`null-seam.test.ts:140`, `objectstack-ai#10965` to `objectstack-ai#10966`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`2555410dd0a7`, `0c5bf5e7e190`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-package` (`.changeset/20596-service-package-provenance-anchors.md`) is included. Its body is stage 9's, word for word, with the package name changed. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After the build, `ab47f6974` appears once in each of `dist/index.d.ts` and `dist/index.d.cts`: the rewritten docblock sits on the exported `PACKAGE_SEAM_UNREADABLE_MESSAGE`. The other rewritten comments do not reach `dist` (0 for `3a7ec2d3b`, `38bc74ed1` and `001a83b04`, and 0 for `ab47f6974` in `index.js` and `index.cjs`). Positive control: the unchanged line 「Like {@link PACKAGE_PUBLISH_DRIVER_FAULT_MESSAGE}, a CONSTANT that」, in the same docblock, is found once in each declaration file. A never-written negative phrase appears nowhere in `dist`. None of the 5 dead numbers is left in `dist`. ## Gates (head `ffd2f1ed2`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test, 114 cases, 8 batteries) exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 1 citation in 1 file and found it on the board: `objectstack-ai#10677`, which already stood on its line. - **Doc authoring:** `pnpm check:doc-authoring` exits 0; the sibling-package prose-id baseline holds (808 pinned sites, no growth). - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `ffd2f1ed2` derived 62 commands: all 56 derived at dispatch, plus `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. Each ran with its exit code captured before any pipe, and all 62 exit 0. `--ran`, fed each command with its exit code, reports 62 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/service-package test`: 5 files pass and 79 tests pass. `vitest list --filesOnly` names 5 files, all the tracked test files, the 4 touched ones included. - `pnpm --filter @objectstack/service-package typecheck` exits 0. `tsc --listFiles` holds all 6 files under `src/`, all 5 touched files included. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 5 touched `.ts` files gives 5 files, 0 errors and 0 warnings. All 5 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 6 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word 「option」. In this package: `#N-word` none, `#A/#B` none, `option #N` none, at the base and at the head. The raw scan agrees: nothing sits beyond the gate's grammar here. - **「This card」 phrases are left.** 14 comment lines in 5 files of this package speak of 「this card」, 「the card」 or 「The card」. They carry no number and neither instrument sees them. One title line was worded so that its neighbour keeps a referent (`null-seam.test.ts:4`, above); the rest are unchanged, as in stages 8 and 9. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#10788` → `3a7ec2d3b`; `objectstack-ai#10964` → `38bc74ed1`. The other three reuse sibling stages' anchors: `objectstack-ai#10965` → `ab47f6974` and `objectstack-ai#10789` → `38bc74ed1` (the `runtime` stage), `objectstack-ai#16650` → `001a83b04` (the `rest` stage). - **Base.** The branch is on `main` at `4dfff176b`. `main` has since moved four commits (`03cdb9a5c`, `b785c3b11`, `5a23096ca`, `01e78dcee`). Their 40 files touch nothing under `service-package`, nor `scripts/check-issue-citations.mjs` or `.changeset/config.json`; the `doc-authoring-prose-id` baseline they shrink has no `service-package` row. So no merge was taken; the merge queue rebuilds on the merged generation. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ts that decided them (objectstack-ai#20757) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the eleventh stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/plugins/plugin-email/src/**` and nothing else. By the seat's census at the claim (`5902547086`), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 10 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR objectstack-ai#20737 as `4dfff176b`, PR objectstack-ai#20742 as `697845d19`). That is **16 sites on 16 lines in 8 files, covering 4 numbers**: - 7 census sites (every census site this package has); - 9 sites in test comments, which the census defers. Three of them carry `objectstack-ai#13190`, a dead number that stands only in test files here, so the census never judged it; it was read on its own (404); - no site the gate's grammar cannot see (the package has none that is dead, see Acceptance notes). Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **4 distinct shas**. No number in this package has an ADR or ruling record of its own (a grep of `docs/adr/` and `scripts/adr-anchors/` finds only ADR-0131 naming `objectstack-ai#11741`, as evidence in its D7, not as the record of that decision; nothing else under `docs/` names the four), so every anchor is a commit, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (16 lines out, 16 in, over 8 files), so no line citation into these files moves. Every one of the 16 changed lines carried a dead citation; there is no reflow line. No code token moves (see the guard below). **No citation number is added.** The added lines carry no tracker number at all. Over the whole diff, added minus removed is negative for the four dead numbers and zero for every other number, and no number is new to the diff. No PR number is the citation on an added line: the two `PR objectstack-ai#8675` spellings became that pull request's squash commit. 10 dead sites are left on purpose, all of them `describe` / `it` titles (see the list below). One more file: a `patch` changeset for `@objectstack/plugin-email`, because the rewritten prose ships (see Changeset below). ## Census: `plugin-email`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/plugins/plugin-email/`. Each run counts as a reading only because its board frontier equals the newest issue or pull-request number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | plugin-email sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `97005aed0`, run 2026-09-30T02:00:45Z to 02:04:02Z | enumerated, 186 pages, frontier objectstack-ai#20748 (newest objectstack-ai#20747 before, objectstack-ai#20748 after: a pull request opened at 02:03:20Z, inside the run) | 1,064 | **7** | 7 | 4 | 3 | | after | head `15a7d69a7`, run 02:11:19Z to 02:14:30Z | enumerated, 186 pages, frontier objectstack-ai#20753 (newest objectstack-ai#20753 before and after) | 1,057 | **0** | 0 | 0 | 0 | The before count matches the seat's census and A1 (7 sites: `objectstack-ai#13189` ×4, `objectstack-ai#11741` ×2, `objectstack-ai#8675` ×1). The before run's board moved during the run; its frontier equals the newest number at the run's end, which is A1's criterion (stage 7's precedent). The whole-repo drop is 7, exactly this diff's census sites. The `resolves` tally is 33,029 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `15a7d69a7`; the head `23283d394` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `plugin-email/src` (50 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction, 37,072 rows) and did not report it. The eleven numbers the census never saw, because they stand only in test files or as the second half of a slash pair here, were read one by one on the issues endpoint: `objectstack-ai#13190` answers 404; `objectstack-ai#5169`, `objectstack-ai#5286`, `objectstack-ai#10619`, `objectstack-ai#16506`, `objectstack-ai#20374`, `objectstack-ai#5197` answer 200 as issues, and `objectstack-ai#8348`, `objectstack-ai#5191`, `objectstack-ai#5211`, `objectstack-ai#5232` as pull requests. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `97005aed0` | 360 | **26** | 7 | 9 | 0 | 10 | | after, `15a7d69a7` | 344 | **10** | 0 | 0 | 0 | 10 | Its src-comment column equals the census's 7, which is the control on the second instrument. The 323 live citations are the same in both readings, and the drop of 16 citations is exactly the rewritten sites. 11 extracted tokens are not tracker references at all and are not judged: the HTML entity `&objectstack-ai#39;` (6 sites in the template engine and its tests) and the fixture subjects `Invoice objectstack-ai#42` to `Invoice objectstack-ai#45` (5 sites). A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 371 occurrences and 26 dead before, 355 and 10 after. Beyond the gate's grammar it sees 11 tokens, none dead: the nine second numbers of the `#A/#B` lines (all live), the excused `Prime Directive objectstack-ai#12`, and the CSS colour `#2563eb`. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (`merge-base --is-ancestor` exit 0 for all 16 line and anchor pairs). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#13189` | 13/4 | 8/5 | `33fbd3566` (PR objectstack-ai#13375): the SMTP port guard tests integrality (`Number.isInteger`), so a fractional port such as `587.5` is refused at construction, and the generated refusal sentence reads `(expected an integer 1-65535)`, the range still rendered from the constants. Its changeset headline names `objectstack-ai#13189`; its diff writes the integrality docblocks the rewritten lines sit in. New to the sweep | | `objectstack-ai#13190` | 5/1 | 3/2 | `56c5b1dbe` (PR objectstack-ai#13316): `smtpOptionsFromMailSettings` passes a present-but-unreadable `smtp_port` through to the guard instead of omitting it (which had silently fallen back to 587); absent and `''` still mean "not set", and no second refusal was added. Its changeset headline names `objectstack-ai#13190`; its diff writes the `objectstack-ai#13190` comment block itself. New to the sweep | | `objectstack-ai#11741` | 6/3 | 3/3 | `b706af987` (PR objectstack-ai#11839): `SendEmailInput` / `SendTemplateInput` gain an optional `organizationId`, which `plugin-email`'s writer stamps verbatim onto `sys_email.organization_id` (pass-through only, no resolution or fabrication), and `sendTemplate` forwards it as a producer of `send()`. Its message names `objectstack-ai#11741` as the card that commit closed; `git blame` puts all three rewritten lines in it. The `plugin-auth` stage's anchor for the same number | | `objectstack-ai#8675` | 2/2 | 2/0 | `c9f595083`: the squash commit of the pull request that was `objectstack-ai#8675` (its subject ends `(objectstack-ai#7987) (objectstack-ai#8675)`): `sys_account`'s OAuth token columns are declared `internal: true`. Its diff records the trap both lines describe: those columns are `required: false`, so inferring "key missing, therefore the strip ran" broke ordinary sign-in (16 red tests), which is why the readback carries the `absenceProvesStrip` discriminator. New to the sweep | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 4), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 4; control leg: stage 1's landing `422db788a` exit 0; the history is complete, `--is-shallow-repository` false, 15,155 commits). Each of the 4 numbers answers 404 on the issues endpoint, which serves pull requests too. Independently, the package's own shipped `CHANGELOG.md` pairs `b706af9`, `33fbd35` and `56c5b1d` with the same three decisions. ## Wordings to check - **Tag swaps in parentheses.** 「(objectstack-ai#13189)」 became 「(commit 33fbd35)」 at `transports/smtp-port-contract.ts:87` (a section heading), `:134` and `transports/smtp.ts:68`. - **Line openers.** 「objectstack-ai#11741 —」 became 「Commit b706af9 —」 at `email-service.ts:742` and `:1439`; 「objectstack-ai#13190 —」 became 「Commit 56c5b1d —」 at `transports/smtp.test.ts:221`; 「## objectstack-ai#13189 —」 became 「## Commit 33fbd35 —」 at `transports/smtp-port-contract.test.ts:34`. - **`email-service.test.ts:342`**, a section rule: 「── objectstack-ai#11741 —」 became 「── Commit b706af9 —」, and its trailing rule was shortened by 10 characters so the line keeps its width exactly. - **`internal-header-readback.ts:37`.** 「(PR objectstack-ai#8675 hit exactly this on `sys_account`'s optional」 became 「(Commit c9f5950 records exactly this on `sys_account`'s optional」: a commit does not "hit" a trap, it records one, and that commit's own diff is where the 16 red tests are recorded. - **`email-headers-internal.integration.test.ts:251`.** 「The regression PR objectstack-ai#8675 measured on a sibling card」 became 「The regression commit c9f5950 records from a sibling card」, the same reading. - **`transports/smtp-port-contract.test.ts:228`.** 「objectstack-ai#13189 is the card that SPENDS that」 became 「Commit 33fbd35 is the change that SPENDS that」, so the noun matches the anchor. - **`transports/smtp.ts:127`, `transports/smtp.test.ts:272`, `:276`, `:281`, `:283`.** The number became 「commit SHA」 in place (「until commit 33fbd35:」, 「The bucket commit 56c5b1d never had to name」, 「Commit 33fbd35 made the guard test」, 「Commit 56c5b1d's rule is that」, 「commit 33fbd35 changed which numbers」). ## The 10 sites left - **Test strings, 10 sites on 9 lines**, all `describe` / `it` titles, left as stages 1 to 10 left theirs: `email-service.test.ts:349` and `send-template.test.ts:63`, `:88` (`objectstack-ai#11741`); `transports/smtp-port-contract.test.ts:225`, `:309`, `:340` (`objectstack-ai#13189`); `transports/smtp.test.ts:230` (`objectstack-ai#13190`), `:271` (`objectstack-ai#13189`), `:293` (`objectstack-ai#13190` and `objectstack-ai#13189`). - No source string, operator log string, assertion message, quoted maintainer ruling or generated file in this package carries a dead number. - Outside `src`, the package's `CHANGELOG.md` names three of these numbers on 5 lines. It is release-owned and deliberately not edited here (see Acceptance notes). ## Mechanical guard: no code token moves The guard compares the TypeScript parser's leaf nodes (a `forEachChild` walk, so comments are trivia and JSDoc nodes are never visited), base `97005aed0` against head. String and template literals are therefore read in full. It ran over all 8 touched `.ts` files. - Real run: 7,035 base leaf tokens, **0 files with a token change** (exit 0). - Comment control in `email-service.ts` (「no resolution, no default, no fabrication」 to 「… no default and no fabrication」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `transports/smtp.ts` (`isValidSmtpPort(port)` given `as number`): DIFFER, 587 to 588 leaf tokens (exit 1). - Positive control, one digit changed inside a kept test title (`transports/smtp.test.ts:293`, `objectstack-ai#13189` to `objectstack-ai#13188`): DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs` (wrap mode) under a shell trap that restores by absolute path, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`1e99bd5e2bcb`, `46c13267611b`, `da5314910bc4`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/plugin-email` (`.changeset/20596-plugin-email-provenance-anchors.md`) is included. Its body is stage 10's, word for word, with the package name changed. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. After the build, `b706af987` appears twice in each of `dist/index.js` and `dist/index.mjs` (the two inline comments in `email-service.ts`, which the bundle keeps). `c9f595083` appears once in each of `dist/index.d.ts` and `dist/index.d.mts` (the `internal-header-readback.ts` docblock), and so does `33fbd3566` (the docblock on `SmtpTransportOptions.port`). `56c5b1dbe` reaches nothing (test files only). Positive controls, one unchanged line beside each shipped rewrite, land exactly where their neighbours do: 「context, so the input's organization is the one fact it may stamp:」 and 「caller's organization so the sys_email row it persists is stamped.」 once in each JS file; 「token columns: inheriting」 and the unchanged line just above the rewritten one in the `port` docblock once in each declaration file. A never-written negative phrase appears nowhere in `dist`. None of the 4 dead numbers is left in `dist`. ## Gates (head `23283d394`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run found no citation added against `97005aed0` (4 files read; test files are a deferred surface). - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `23283d394` derived 61 commands: all 55 derived at dispatch, plus `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. Each ran with its exit code captured before any pipe, and all 61 exit 0. `--ran`, fed each command with its exit code, reports 61 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/plugin-email test`: 31 files pass and 510 tests pass. `vitest list --filesOnly` names 31 files, all the tracked test files, the 4 touched ones included. - `pnpm --filter @objectstack/plugin-email typecheck` exits 0 (`tsc` on `tsconfig.json`, then `check:test-typecheck` on `tsconfig.test.json`: 0 files and 0 errors in its debt ledger). `tsc --listFiles` holds all 8 touched files in both programs, and the test program holds all 50 files under `src/`. - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 8 touched `.ts` files, gives 8 files, 0 errors and 0 warnings. All 8 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 9 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word 「option」. In this package: `#N-word` none, `#A/#B` 9 lines, `option #N` none, at the base and at the head, which is the claim's 0 / 9 / 0. Every second number on the 9 slash lines answers 200 (`objectstack-ai#5197` ×2, `objectstack-ai#5191`, `objectstack-ai#5211`, `objectstack-ai#5232` ×2, `objectstack-ai#5177`, `objectstack-ai#4251`, `objectstack-ai#5094`), so nothing there needed rewriting. - **ADR-0131 names `objectstack-ai#11741`.** Its D7 cites `objectstack-ai#11741` as the writer fact that keeps `sys_email` tenant data. That is evidence inside a later record, not the record of what `objectstack-ai#11741` decided, so it is not this stage's anchor, and `docs/adr/**` is a governed Tier H surface outside this card's stages. It joins the ADR-tree residue the seat already carries (ADR-0131's `objectstack-ai#14484`, stage 2). - **`CHANGELOG.md` is left.** `packages/plugins/plugin-email/CHANGELOG.md` names `objectstack-ai#11741`, `objectstack-ai#13189`, `objectstack-ai#13190` and `objectstack-ai#8675` on 5 lines. It is release-owned (AGENTS.md, Documentation Guardrails), a deferred surface of the citation gate, and ⛔ not part of this stage. - **「This card」 phrases are left.** 20 comment lines in 8 files of this package speak of 「this card」, 「the card」 or 「the two cards」. They carry no number and neither instrument sees them. Inside the `objectstack-ai#13189` test block, they still have the kept `(objectstack-ai#13189)` title as their referent; the one rewritten line that said 「the card」 now says 「the change」 (above). The rest are unchanged, as in stages 8 to 10. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#13189` → `33fbd3566`; `objectstack-ai#13190` → `56c5b1dbe`; `objectstack-ai#8675` → `c9f595083`. `objectstack-ai#11741` → `b706af987` reuses the `plugin-auth` stage's anchor. - **Base.** The branch is on `main` at `97005aed0`. `main` has since moved two commits (`9c8f113c6`, `a6866da0c`). Their 14 files touch nothing under `plugin-email`, nor `scripts/check-issue-citations.mjs`, `.changeset/config.json` or the `doc-authoring-prose-id` baseline, and the three console-injection scripts they change are not among this diff's 61 derived families. So no merge was taken; the merge queue rebuilds on the merged generation. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ommits that decided them (objectstack-ai#20775) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the twelfth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/triggers/trigger-schedule/src/**` and nothing else. By the seat's claim (`5903462246`), it is the largest package in the lane that no in-flight work holds, now that objectstack-ai#20599's PR objectstack-ai#20746 (which edited `time-relative-trigger.ts`) has landed. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 11 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR objectstack-ai#20737 as `4dfff176b`, PR objectstack-ai#20742 as `697845d19`, PR objectstack-ai#20757 as `cba417a8f`). That is **32 sites on 32 lines in 6 files, covering 2 numbers**: - 18 census sites (every census site this package has); - 14 sites in test comments, which the census defers; - no site the gate's grammar cannot see (the package has none, see Acceptance notes). Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **2 distinct shas**. Neither number has an ADR or ruling record of its own (a grep of `docs/adr/`, `scripts/adr-anchors/` and the rest of `docs/` for both numbers finds nothing, and no ADR records the acting-organization decision or the driver-memory per-call refusal), so both anchors are commits, per ruling C's order. No number was dropped. Only comments changed. Every touched source file keeps its line count (32 lines out, 32 in, over 6 files), so no line citation into these files moves. Every one of the 32 changed lines carried a dead citation; there is no reflow line. No code token moves (see the guard below). **No citation number is added.** The only tracker number on an added line is the live `objectstack-ai#8844`, on the line it already stood on. Added minus removed is negative for the two dead numbers and zero for every other number, and no number is new to the diff. No PR number is the citation on an added line. 4 dead sites are left on purpose: three `describe` titles, and one comment that quotes one of those titles verbatim (see the list below). One more file: a `patch` changeset for `@objectstack/trigger-schedule`, because the rewritten prose ships (see Changeset below). ## Census: `trigger-schedule`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/triggers/trigger-schedule/`. Each run counts as a reading only because its board frontier equals the newest issue or pull-request number, read by a separate request just before and just after the run. | reading | tree | board | whole-repo `allocated-but-absent` | trigger-schedule sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `cba417a8f`, run 2026-09-30T03:30:14Z to 03:33:24Z | enumerated, 186 pages, frontier objectstack-ai#20769 (newest objectstack-ai#20769 before and after) | 823 | **18** | 18 | 2 | 2 | | after | head `226be8050`, run 03:37:59Z to 03:41:09Z | enumerated, 186 pages, frontier objectstack-ai#20769 (newest objectstack-ai#20769 before and after) | 805 | **0** | 0 | 0 | 0 | The before count matches the seat's census and A1 (18 sites: `objectstack-ai#16659` ×17 and `objectstack-ai#16589` ×1, in `schedule-trigger.ts` ×5 and `time-relative-trigger.ts` ×13). A1 noted that PR objectstack-ai#20746 edited `time-relative-trigger.ts` today; the before count above is taken on the base that already holds that edit. The whole-repo drop is 18, exactly this diff's census sites. The `resolves` tally is 33,038 in both runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. The after run was taken on `226be8050`; the head `14314f49c` adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `trigger-schedule/src` (14 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when that census judged it on this board anywhere (its `--list` extraction, 36,840 rows) and did not report it. Every number this package cites is covered by one or the other, so no number needed a separate read to be judged; the two dead numbers were also read one by one on the issues endpoint, and each answers 404. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `cba417a8f` | 159 | **36** | 18 | 15 | 0 | 3 | | after, `226be8050` | 127 | **4** | 0 | 1 | 0 | 3 | Its src-comment column equals the census's 18, which is the control on the second instrument. The 123 live citations are the same in both readings, and the drop of 32 citations is exactly the rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 162 occurrences and 36 dead before, 130 and 4 after. Beyond the gate's grammar it sees 3 tokens, none a tracker reference: the maintainer decision-batch ordinals `batch objectstack-ai#13`, `objectstack-ai#116` and `objectstack-ai#118`, which the gate's `NON_CITATION_HEADS` excuses by design. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, and `git blame` at the base puts every rewritten line in its anchor commit or in a later commit that descends from it (21 lines blame to the anchor itself; for the other 11, `merge-base --is-ancestor` of anchor and blamed commit exits 0). | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#16659` | 34/6 | 30/4 | `ecdfc9411` (PR objectstack-ai#17334): a time-triggered flow (`schedule` or `time_relative`) declares its acting organization on its start node as `config.organization`; the engine lifts it onto the binding; both time triggers refuse to bind a flow that declares none, naming it at `error` and THROWING so the engine records the refusal instead of reporting the flow bound; the run carries the declared organization as `tenantId`; and the time-relative sweep's own query carries it too, so the sweep SELECTS inside that organization (the review finding F2 its diff names), with a store that cannot honour the scope reported at `error` and an object the engine exempts from scoping disclosed at bind. Its body names `objectstack-ai#16659` twice (the three consequences pinned on both drivers, and the proof registered), and its diff names it on 65 added lines. The anchor the spec stage (`0f6dcac5e`) and the lint stage (`f29c83db1`) already give the same number | | `objectstack-ai#16589` | 2/2 | 2/0 | `555a89cbd` (PR objectstack-ai#17005): `driver-memory` gains a third seam, `assertCallNotTenantScoped`, called first in every driver door that accepts `DriverOptions`, which REFUSES a call the engine tenant-scoped instead of discarding the scope and answering every organization's rows; row-level isolation is deliberately not implemented. Its message does not carry the number, but its own diff writes the mechanism the two lines describe and names `objectstack-ai#16589` 30 times (the `[objectstack-ai#16589] Seam 3` markers and the guard's docblock), so it is the commit that decided it. New to the sweep | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 2), and both are ancestors of the base (`merge-base --is-ancestor`, exit 0 for both; control leg: stage 1's landing `422db788a` exit 0; reverse leg, base against `ecdfc9411`, exit 1; the history is complete, `--is-shallow-repository` false, 15,160 commits; each anchor lies deeper than the control, 1,616 and 1,814 commits behind the base). Each of the 2 numbers answers 404 on the issues endpoint, which serves pull requests too. Independently, the package's own shipped `CHANGELOG.md` pairs `ecdfc94` with `objectstack-ai#16659` (line 149) and `assertCallNotTenantScoped` with `objectstack-ai#16589` (line 238). ## Wordings to check - **Tag swaps in brackets or parentheses.** 「[objectstack-ai#16659]」 became 「[commit ecdfc94]」 on 18 lines, 「(objectstack-ai#16659)」 became 「(commit ecdfc94)」 at `schedule-trigger.ts:251`, `:372` and `time-relative-trigger.ts:50`, and 「(objectstack-ai#16589)」 became 「(commit 555a89c)」 at `time-relative-trigger.ts:615` and `time-relative-trigger.test.ts:988`. - **Section rules.** `schedule-trigger.test.ts:327`, `time-relative-trigger.test.ts:794` and `:862`: the 16-character phrase replaces the 6-character number and the trailing rule loses 10 characters, so each line keeps its width exactly. The `:862` heading keeps 「F2」 beside the sha; F2 is the selection finding `ecdfc9411`'s own diff names. - **「before objectstack-ai#16659」** at `time-relative-trigger.ts:365` and `:543` became 「before commit ecdfc94」: before that commit the sweep queried with `isSystem` alone, which is the unscoped selection both sentences describe. - **「the objectstack-ai#16659 defect」** at `time-relative-trigger.ts:561` and `time-relative-trigger.test.ts:1371` became 「the defect commit ecdfc94 fixed」: a commit fixes a defect, it is not one, and the widening both sentences name is the selection half that commit closed. - **`schedule-trigger.test.ts:512`.** 「the exact defect objectstack-ai#16659's own refusal was shaped to avoid」 became 「the exact defect commit ecdfc94's own refusal was shaped to avoid」: the defect is a refusal that logs and arms anyway, and that commit is where the refusal became a throw so the engine records it. - **`schedule-trigger.test.ts:588`.** 「(the objectstack-ai#16659 suite above)」 became 「(commit ecdfc94's refusal suite above)」, so the pointer still lands on the refusal suite at `:337`. ## The 4 sites left - **Test strings, 3 sites on 3 lines**, all `describe` titles, left as stages 1 to 11 left theirs: `schedule-trigger.test.ts:337` and `:462`, `time-relative-trigger.test.ts:805` (all `objectstack-ai#16659`). - **One comment that quotes a kept title verbatim:** `schedule-trigger.test.ts:71` points the reader at 「`ScheduleTrigger — the acting-organization refusal (objectstack-ai#16659)` below」, the exact text of the `describe` title at `:337`. The number there belongs to the quotation, so it stays with the title it quotes: rewriting it would point at a title that does not exist. It moves when the title does. - No source string, operator log string, assertion message, quoted maintainer ruling or generated file in this package carries a dead number. - Outside `src`, the package's `CHANGELOG.md` names `objectstack-ai#16659` on 6 lines and `objectstack-ai#16589` on 2 (lines 149, 153, 238, 273, 297, 300, 302, 304). It is release-owned and deliberately not edited here (see Acceptance notes). The package `README.md`, which also ships, names neither number. ## Mechanical guard: no code token moves The guard compares, base `cba417a8f` against head, over all 6 touched `.ts` files: - **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild` walk, so comments are trivia and JSDoc nodes are never visited). String and template literals are therefore read in full. - **Reading 2**, the full token stream in parser context (a `getChildren` walk, so punctuation and keywords are included; JSDoc nodes skipped). Results: - Real run: 10,192 base leaf tokens, **0 files with a token change** on either reading (exit 0). - Comment control in `schedule-trigger.ts` (「the same way `schedule` is.」 to 「the same way as `schedule`.」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `time-relative-trigger.ts` (`resolveBindingOrganization(binding)` given `as FlowTriggerBinding`): DIFFER, 1,215 to 1,216 leaf tokens and 2,760 to 2,762 full tokens (exit 1). - Positive control, one digit changed inside a kept test title (`schedule-trigger.test.ts:462`, `objectstack-ai#16659` to `objectstack-ai#16658`): DIFFER on the string literal (exit 1). Every mutation went through `scripts/ablation-replace.mjs` (wrap mode) under a shell trap that restores by absolute path, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`651170483856`, `c85aadbd168d`, `78a5dea4a463`), with `git diff HEAD` empty and a clean tree afterwards. A first version of reading 2 used TypeScript's context-free scanner and was discarded before any control ran: it opened template tokens on backticks it could not place and swallowed comment text into them, so it reported comment edits as token changes (4 files) while reading 1 read 0. The parser-context stream replaced it, and every figure above is from the replacement. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/trigger-schedule` (`.changeset/20596-trigger-schedule-provenance-anchors.md`) is included. Its body is stage 11's, word for word, with the package name changed. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. After the build: - `ecdfc9411` appears 3 times in each of `dist/index.js` and `dist/index.mjs`: the inline comments at `schedule-trigger.ts:777` and `time-relative-trigger.ts:585` and `:702`, which the bundle keeps. - It appears twice in each of `dist/index.d.ts` and `dist/index.d.mts`: the `FlowTriggerBinding.organization` docblock (`schedule-trigger.ts:32`) and the sweep-context docblock (`time-relative-trigger.ts:50`). - `555a89cbd` appears once in each JS entry (`time-relative-trigger.ts:615`). - Positive controls, one unchanged line beside each shipped rewrite, land exactly where their neighbours do: four neighbours once in each JS file and 0 in the declaration files, and two once in each declaration file and 0 in the JS files. - A never-written negative phrase appears nowhere in `dist`. - Neither dead number is left in `dist`. ## Gates (head `14314f49c`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` exits 0. `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 1 added citation across 2 files, the live `objectstack-ai#8844`, and it resolves. - **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the sibling-package prose-id baseline holds, no growth). - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `14314f49c` (after a fresh fetch) derived 59 commands. They are all 53 derived at dispatch, plus `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. - Each ran with its exit code captured before any pipe, and all 59 exit 0. - `--ran`, fed each command with its exit code, reports 59 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. - A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock:** - `pnpm --filter @objectstack/trigger-schedule test`: 8 files pass and 170 tests pass. `vitest list --filesOnly` names 8 files, all the tracked test files, the 4 touched ones included. - `pnpm --filter @objectstack/trigger-schedule typecheck` exits 0, and `tsc --listFiles` holds all 14 files under `src/`, the 6 touched ones included. - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 6 touched `.ts` files, gives 6 files, 0 errors and 0 warnings (its `--format json` output). All 6 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 7 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#` (objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word 「option」. In this package: `#N-word` none, `#A/#B` none, `option #N` none, at the base and at the head, which is the claim's 0 / 0 / 0. The two `pre-objectstack-ai#10220` spellings in `time-relative-trigger.test.ts` are extracted by the gate as this repository's `objectstack-ai#10220`, which the census judges live. - **`CHANGELOG.md` is left.** `packages/triggers/trigger-schedule/CHANGELOG.md` names `objectstack-ai#16659` and `objectstack-ai#16589` on 8 lines. It is release-owned (AGENTS.md, Documentation Guardrails), a deferred surface of the citation gate, and ⛔ not part of this stage. - **「The card」 phrases are left.** 8 comment lines in 5 files of this package speak of 「this card」, 「that card」 or 「the card」. They carry no number and neither instrument sees them. The one beside a rewritten line, `schedule-trigger.test.ts:329` (「the card's consequence (3)」), sits under the heading `:327` that now names `ecdfc9411`, whose own message pins those three consequences, so it keeps a referent. The rest are unchanged, as in stages 8 to 11. - **The census instrument did not truncate in this stage.** Both enumerations read 186 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#16589` → `555a89cbd` is new to the sweep; `driver-memory`'s own `src` still names `objectstack-ai#16589` on 29 lines in 4 files (26 of them comments; corrected by the seat from the dev report, which measured it), all outside this lane's stage surface. `objectstack-ai#16659` → `ecdfc9411` reuses the spec and lint stages' anchor. - **Base.** The branch is on `main` at `cba417a8f`. `main` has since moved three commits (`0d9349fea`, `7053333e1`, `f284ab26d`). Their 9 files are one changeset, ADR-0053, and sources and tests under `service-analytics` and `service-automation`. They touch nothing under `trigger-schedule`, nor `scripts/check-issue-citations.mjs`, `.changeset/config.json` or the `doc-authoring-prose-id` baseline. `service-automation` is a dev dependency of this package, but this diff moves no code token, so nothing here can interact with it. No merge was taken; the merge queue rebuilds on the merged generation. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…the commits that decided them (objectstack-ai#20789) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the thirteenth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/triggers/trigger-record-change/src/**` and nothing else. By the seat's claim (`5904332626`), it is the largest package in the lane that no in-flight work holds, while `service-automation` stays held behind objectstack-ai#20726. Later stages cover the other packages, so this PR says `Part of` and the card stays open. Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), by the method of stages 1 to 12 (PR objectstack-ai#20609 as `422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as `9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`, PR objectstack-ai#20729 as `d2820876f`, PR objectstack-ai#20737 as `4dfff176b`, PR objectstack-ai#20742 as `697845d19`, PR objectstack-ai#20757 as `cba417a8f`, PR objectstack-ai#20775 as `91e8fa194`). That is **29 sites on 29 lines in 5 files, covering 3 numbers**: - 6 census sites (every census site this package has, all `objectstack-ai#14744`); - 23 sites in test comments, which the census defers: 17 more of `objectstack-ai#14744`, 1 of `objectstack-ai#13657`, and 5 of `objectstack-ai#11081`. `objectstack-ai#11081` stands only in a test file here, so the census never judged it; it was read on its own and answers 404. Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what was decided: **4 distinct shas**. None of the three numbers has an ADR or ruling record of its own, so every anchor is a commit, per ruling C's order (see the per-number table). No number was dropped. Only comments changed. Every touched source file keeps its line count (30 lines out, 30 in, over 5 files), so no line citation into these files moves. 29 of the 30 changed lines carried a dead citation; the thirtieth keeps a referent the rewrite would otherwise have removed (see Wordings). No code token moves (see the guard below). **No citation number is added.** The only tracker numbers on added lines are the live `objectstack-ai#15356` (3 times) and `objectstack-ai#8738` (once), each on the line it already stood on. Added minus removed is negative for the three dead numbers and zero for every other number, and no number is new to the diff. No PR number is the citation on an added line. 4 dead sites are left on purpose, all test titles (see the list below). One more file: a `patch` changeset for `@objectstack/trigger-record-change`, because the rewritten prose ships (see Changeset below). ## Census: `trigger-record-change`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/triggers/trigger-record-change/`. Each run counts as a reading only because its board frontier equals the newest issue or pull-request number, read by a separate request just before and just after the run. In all three runs a new number was opened while the run was enumerating; each frontier equals the newest number at the run's end, which is the criterion (stages 7 and 11 met the same shape). | reading | tree | board | whole-repo `allocated-but-absent` | trigger-record-change sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `91e8fa194`, run 2026-09-30T04:58:08Z to 05:01:28Z | enumerated, 187 pages, frontier objectstack-ai#20779 (newest objectstack-ai#20778 before, objectstack-ai#20779 after) | 802 | **6** | 6 | 2 | 1 | | after | `bb9d39a87` (the comments commit), run 05:07:54Z to 05:11:48Z | enumerated, 187 pages, frontier objectstack-ai#20780 (newest objectstack-ai#20779 before, objectstack-ai#20780 after) | 796 | **0** | 0 | 0 | 0 | | after, final head | head `bbfe7cb24`, run 05:39:10Z to 05:42:26Z | enumerated, 187 pages, frontier objectstack-ai#20784 (newest objectstack-ai#20783 before, objectstack-ai#20784 after) | 796 | **0** | 0 | 0 | 0 | The before count matches the seat's census and A1 (6 sites, all `objectstack-ai#14744`: `decouple-flow-record.ts` ×1 and `record-change-trigger.ts` ×5). The whole-repo drop is 6, exactly this diff's census sites. The `resolves` tally is 33,055 in all three runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did not move either. No run was truncated or discarded: all three enumerations read 187 pages at the newest frontier. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `namesThisRepository` over every `.ts` file under `trigger-record-change/src` (14 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported it `allocated-but-absent`, and alive when the gate's own census-scope extraction (36,836 citations over 2,617 files) judged it and the census did not report it. Five numbers are covered by neither, because they stand only in test files: each was read on its own. `objectstack-ai#11081` answers 404; `objectstack-ai#5715` and `objectstack-ai#17982` answer 200 as pull requests; `objectstack-ai#5785` and `objectstack-ai#17985` answer 200 as issues. The three dead numbers were also read one by one, and each answers 404. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `91e8fa194` | 186 | **32** | 6 | 23 | 0 | 3 | | after, `bbfe7cb24` | 157 | **3** | 0 | 0 | 0 | 3 | Its src-comment column equals the census's 6, which is the control on the second instrument. The 154 live citations are the same in both readings, and the drop of 29 citations is exactly the rewritten sites. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 195 occurrences before and 166 after. Beyond the gate's grammar it sees 9 tokens, the same at base and head: the second number of five `#A/#B` pairs (only one is dead, the kept title at `before-update-flow-payload-reach.test.ts:872`), two `/objectstack-ai#3457/` regex literals in assertions (live), and two `PD objectstack-ai#12` ordinals. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#14744` | 27/4 | 22/4 | `4f85e4d11` (PR objectstack-ai#15475): the flow-facing `record` (and its `params` alias) and `previous` are decoupled from the engine's own objects before a flow runs (`decoupleFromEngineState`: arrays, plain objects, `Date`, `RegExp`, `Map` and `Set` are copied, primitives, functions and other class instances shared), so a flow mutating a nested value in place no longer writes the batch payload that ADR-0058 Addendum II D3 shares across every row of a `multi: true` update. A COPY rather than a FREEZE, because `expandDeclaredLookups` writes into the record it is handed. The engine's write shape is unchanged, and the same-key per-row-value residue is deliberately left unguarded. Its changeset records the maintainer's option-A ruling on `objectstack-ai#14744` in its own words, its diff names `objectstack-ai#14744` on 29 added lines, and it created `decouple-flow-record.ts` and both of this package's pin files. `git blame` at the base puts every one of the 22 lines in this commit. New to the sweep | | `objectstack-ai#14744` (the census line) | (in the row above) | 1/0 | `03c1b0f6f` (PR objectstack-ai#15301): the census of same-key / per-row-VALUE `beforeUpdate` rewrites, which found ZERO across 23 production registration sites and recorded the `buildContext` overlay conclusion as a source reading, not a measurement. Its message names `objectstack-ai#14744` four times and states that result word for word. `before-update-flow-payload-reach.test.ts:29` describes this census, not the fix, so it cites the census commit, by the per-arm precedent of stages 5 and 9. The line was written by `4f85e4d11`, which descends from `03c1b0f6f` (`merge-base --is-ancestor` exit 0). New to the sweep | | `objectstack-ai#13657` | 1/1 | 1/0 | `b003cf2e8` (PR objectstack-ai#13864): the post-hook half of the declared-field door, which refuses an undeclared field a before-hook writes, with one envelope on every driver. Its message names `objectstack-ai#13657` seven times. The runtime and lint stages' anchor for the same number. The line was written by `4f85e4d11`, which descends from it (exit 0) | | `objectstack-ai#11081` | 5/1 | 5/0 | `c28e4cfae` (PR objectstack-ai#11570): the two SqlDriver-backed fixtures stop blanket-silencing their kernel and carry `@objectstack/runtime`'s shared expected-noise capture, which withholds only a declared table's own `no such table` line, forwards every other driver fault, and lets `afterAll` assert each channel fired. Its message names `objectstack-ai#11081`, and its diff writes the five `[objectstack-ai#11081]` tags in this very file; `git blame` at the base puts all five lines in it. Stage 7's anchor for the same number | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 4), and all 4 are ancestors of the base (`merge-base --is-ancestor`, exit 0 for each; reverse leg, base against each anchor, exit 1 for each; control legs exit 0: stage 1's landing `422db788a`, and the repository's root commit, which lies deeper than every anchor; the history is complete, `--is-shallow-repository` false, 15,167 commits; the anchors lie 2,516, 2,585, 3,082 and 4,207 commits behind the base). Each of the 3 numbers answers 404 on the issues endpoint, which serves pull requests too. No ADR, `scripts/adr-anchors/` file or other `docs/` page records any of the three as its decision. `docs/audits/2026-09-multi-update-per-row-value-census.md` names `objectstack-ai#14744`, but it states that it is "measurement only — ships nothing … implements no guard", the input to a decision rather than its record, so the census line cites the commit that landed it. ## Wordings to check - **Tag swaps in brackets or parentheses.** 「[objectstack-ai#14744]」 became 「[commit 4f85e4d]」 at `decouple-flow-record.test.ts:4` and `before-update-flow-payload-reach.test.ts:805`. 「[objectstack-ai#11081]」 became 「[commit c28e4cf]」 on 5 lines. 「(objectstack-ai#14744, measured by objectstack-ai#15356)」 became 「(commit 4f85e4d, measured by objectstack-ai#15356)」 at `decouple-flow-record.ts:5`. 「(objectstack-ai#14744)」 became 「(commit 4f85e4d)」 at `record-change-trigger.ts:340`. 「(objectstack-ai#8738 pre-hook / objectstack-ai#13657 post-hook)」 became 「(objectstack-ai#8738 pre-hook / commit b003cf2 post-hook)」. - **Headings `:4` and `:859`.** 「[objectstack-ai#15356 measured, objectstack-ai#14744 closed]」 and 「[objectstack-ai#15356 measured it, objectstack-ai#14744 closed it]」 keep the live `objectstack-ai#15356` and put the sha where the dead number stood. - **`before-update-flow-payload-reach.test.ts:10`.** 「objectstack-ai#14744 then ruled the door closed」 became 「The option-A ruling (commit 4f85e4d) then closed the door」: the ruling is named in words beside the commit that carried it, whose changeset records it, the form stages 2, 6 and 7 used for a ruling. - **`:22` and `:87`.** 「the objectstack-ai#14744 residue shape」 and 「the objectstack-ai#14744 pinned residue shape」 became 「the residue shape commit 4f85e4d pins」: the positive control that pins it is in that commit's diff. - **`:23`.** 「because objectstack-ai#14744's fix is about aliasing」 became 「because commit 4f85e4d fixes aliasing」: a commit fixes something, it does not have a fix. - **`:29` and `:34`, the census paragraph.** 「objectstack-ai#14744's census found」 became 「The census in commit 03c1b0f found」. That removed the referent of 「The conclusion recorded on that card」 five lines down, so `:34` became 「The conclusion recorded in that census」. This is the one changed line that carried no dead number. It is true as written: the census record `03c1b0f6f` landed carries that very conclusion, "On a source reading, `buildContext` materialises a *new* record object by overlay … a reading, not a measurement" (`docs/audits/2026-09-multi-update-per-row-value-census.md:308-311`). - **`:455`.** 「that is precisely the blind spot objectstack-ai#14744 is weighing」 became 「… the blind spot commit 4f85e4d left unguarded」. The present tense described a card still being weighed; that commit's changeset says the key-set refusal "is untouched and is not widened — a hook that assigns the same key with per-row values still passes it". - **「Before objectstack-ai#14744」 / 「before objectstack-ai#14744」** at `:686`, `:705`, `:738`, `:924` (the word 「Before」 sits at the end of the line above at `:685` and `:704`) became 「before commit 4f85e4d」: before that commit the flow-facing record shared its nested values with the payload, which is the reading each sentence quotes. - **「objectstack-ai#14744 made」, 「objectstack-ai#14744 carries the fix」, 「objectstack-ai#14744 closed the door」** at `:47`, `:95`, `:642`, 「Until objectstack-ai#14744」 at `record-change-trigger.ts:341`, 「and objectstack-ai#14744.」 at `:124`, 「objectstack-ai#14744 — DECOUPLE」 at `:453`, 「(unchanged by objectstack-ai#14744 —」 at `:496`: the number became the commit, and each sentence already states what the commit did. ## The 4 sites left - **Test strings, 4 sites on 4 lines**, all `describe` / `it` titles carrying `objectstack-ai#14744`, left as stages 1 to 12 left theirs: `before-update-flow-payload-reach.test.ts:825` and `:872` (the second number of `[objectstack-ai#15356/objectstack-ai#14744]`, a spelling the gate's grammar cannot see), `decouple-flow-record.test.ts:78` and `:136`. - No source string, operator log string, assertion message, quoted maintainer ruling or generated file in this package carries a dead number. - Outside `src`, the package's `CHANGELOG.md` names `objectstack-ai#14744` on 2 lines (467, 478). It is release-owned and deliberately not edited here (see Acceptance notes). The package `README.md`, which also ships, names none of the three. ## Mechanical guard: no code token moves The guard compares, base `91e8fa194` against head, over all 5 touched `.ts` files: - **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild` walk, so comments are trivia and JSDoc nodes are never visited). String and template literals are therefore read in full. - **Reading 2**, the full token stream in parser context (a `getChildren` walk, so punctuation and keywords are included; JSDoc nodes skipped). Results: - Real run at the final head `bbfe7cb24`: 6,110 base leaf tokens, **0 files with a token change** on either reading (exit 0). - Comment control in `record-change-trigger.ts` (「reach nothing outside its own run.」 to 「reach nothing beyond its own run.」): 0 files changed, as expected (exit 0). - Positive control, a code token added in `record-change-trigger.ts` (`params: isolatedRecord,` given `as typeof isolatedRecord`): DIFFER, 953 to 954 leaf tokens and 2,130 to 2,133 full tokens (exit 1). - Positive control, one digit changed inside a kept test title (`decouple-flow-record.test.ts:78`, `objectstack-ai#14744` to `objectstack-ai#14745`): DIFFER on the string literal (exit 1). Every mutation went through `scripts/ablation-replace.mjs` (wrap mode) under a shell trap that restores by absolute path, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (`f3235a962fc5`, `9a8bf70abbcc`), with `git diff HEAD` empty and a clean tree afterwards. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/trigger-record-change` (`.changeset/20596-trigger-record-change-provenance-anchors.md`) is included. Its body is stage 12's, word for word, with the package name changed. Measured on the built package (A3), after a full workspace build in which this package was a cache miss: `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. - `4f85e4d11` appears 3 times in each of `dist/index.js` and `dist/index.mjs`: the `buildContext` docblock (`record-change-trigger.ts:340` and `:341`) and the inline comment at `:496`, which the bundle keeps. - It appears twice in each of `dist/index.d.ts` and `dist/index.d.mts`: the same `buildContext` docblock. - The other three anchors appear nowhere in `dist`: their lines are in test files. The rewrites at `record-change-trigger.ts:124` and `:453` and `decouple-flow-record.ts:5` are stripped by the bundle. - Positive controls, one unchanged line beside each rewrite, land exactly where their neighbours do: the line after `:341` once in all four files, the line before `:496` once in each JS file and 0 in the declaration files, and the neighbours of the three stripped rewrites 0 everywhere. - A never-written negative phrase appears nowhere in `dist`. - None of the three dead numbers is left in `dist`. ## Gates (final head `bbfe7cb24`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` exits 0 (self-test, 114 cases, 8 batteries). `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged 1 added citation across 2 files, the live `objectstack-ai#15356` at `decouple-flow-record.ts:5`, and it resolves. - **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the sibling-package prose-id baseline holds, no growth). - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `bbfe7cb24` (after a fresh fetch) derived 59 commands. They are all 53 derived at dispatch, plus `check:engine-double-contract`, `check:objectql-double-limit`, `check:query-options-erasure`, `check:type-check-coverage`, `check:type-check-debt` and `check:where-matcher`. - Each ran with its exit code captured before any pipe, and all 59 exit 0; none exited 3. - `--ran`, fed each command with its exit code, reports 59 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. - A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock, at `bbfe7cb24`:** - `pnpm --filter @objectstack/trigger-record-change test`: 10 files pass and 101 tests pass. `vitest list --filesOnly` names 10 files, all the tracked test files, the 3 touched ones included. - `pnpm --filter @objectstack/trigger-record-change typecheck` exits 0. `tsc --listFiles` on `tsconfig.test.json` holds all 14 files under `src/`, and on `tsconfig.json` the 4 non-test files, so all 5 touched files are compiled. - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 5 touched `.ts` files, gives 5 files, 0 errors and 0 warnings (its `--format json` output). All 5 are in eslint's own population (`isPathIgnored` is false for each; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 6 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked.** `CITATION_RE` refuses a hyphen after the digits and a `/` before the `#`, `NON_CITATION_HEADS` excuses a number after the word 「option」, and a URL-spelled link carries no `#` at all (objectstack-ai#20636). In this package, at the base and at the head: `#N-word` none, `#A/#B` 5 lines, `option #N` none, URL-spelled none, which is the claim's 0 / 5 / 0 / 0. Of the five `#A/#B` second numbers (`objectstack-ai#4251` twice, `objectstack-ai#5038`, `objectstack-ai#4649`, `objectstack-ai#14744`), only `objectstack-ai#14744` is dead, and it stands in a kept test title. - **`CHANGELOG.md` is left.** `packages/triggers/trigger-record-change/CHANGELOG.md` names `objectstack-ai#14744` on 2 lines. It is release-owned (AGENTS.md, Documentation Guardrails), a deferred surface of the citation gate, and ⛔ not part of this stage. - **A live number in a runtime string, left for its lane.** `record-change-trigger.ts:239`'s operator `warn` for an array-form trigger event ends with the live `objectstack-ai#3457`, and two tests assert the message carries it. That is form D, not this card's comment-only form C, and the shrink-only `doc-authoring-prose-id` baseline already holds it (`record-change-trigger.ts`: `objectstack-ai#3457: 1`), so `check:doc-authoring` sees no growth. - **「The card」 phrases are left.** 3 other comment lines in 2 files of this package speak of 「the card」. They carry no number, neither instrument sees them, and none of them lost a referent in this diff. They are unchanged, as in stages 8 to 12. - **The census instrument did not truncate in this stage.** All three enumerations read 187 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#14744` → `4f85e4d11` (the decoupling) or `03c1b0f6f` (its census), both new to the sweep; `objectstack-ai#13657` → `b003cf2e8` and `objectstack-ai#11081` → `c28e4cfae` reuse the runtime and lint stages' anchor and stage 7's. - **Base.** The branch is on `main` at `91e8fa194`. `main` has since moved six commits (`cd6d8a5ff`, `1bcba27d2`, `a3d7588b5`, `9ad654487`, `274e16271`, `085ca6bc1`). Their 50 files touch nothing under `trigger-record-change`, nor `scripts/check-issue-citations.mjs`, `.changeset/config.json` or the `doc-authoring-prose-id` baseline, and none is a path in this diff. Three of them are gate inputs (`scripts/engine-double-contract.pinned.json`, `scripts/objectql-double-limit.baseline.json`, `scripts/sdui-manifest.record.json`), so those families ran here against the base's copies; this diff moves no code token, so nothing here can interact with them. No merge was taken; the merge queue rebuilds on the merged generation. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #20596
Clause-②: no
What changed
This is the fifth stage of the
domain:serviceslane of the dead-citation sweep. It coverspackages/services/service-datasource/src/**and nothing else. By the seat's fresh census at the claim (5894843429), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR saysPart ofand the card stays open.Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on #19123), by the method of stages 1 to 4 (PR #20609 as
422db788a, PR #20626 asb80ab579d, PR #20634 as4d04b6be3, PR #20658 as9a4b2bb38). That is 75 sites on 74 lines in 23 files, covering 16 numbers:Each rewritten line now cites the commit in
origin/mainhistory that decided what the line describes, and says in its own words what was decided: 17 distinct shas.#8696was one card fixed in two halves, so its lines cite the half they describe: the mysql DSN branch (72050cc47) or the mongodb DSN branch (90a12fb18).PR #8588was itself a pull request, and it now cites its squash commit3dede582b. No number in this package has an ADR or ruling record of its own in the repository (a grep ofdocs/adr/for all 16 finds none), so every anchor is a commit, per ruling C's order. No number was dropped.Only comments changed. Every touched source file keeps its line count (78 lines out, 78 in, over 23 files), so no line citation into these files moves. 4 of those 78 lines hold no dead citation; they are reflow, listed under Wordings below. No code token moves (see the guard below).
No citation number is added. Every tracker number on an added line was already on the line it replaces: the only one is
#12482, which resolves and stood ondatasource-connection-service.ts:101before. Over the whole diff, added minus removed is 0 or negative for every number, and no number is new to the diff. No PR number stands on an added line.Thirteen dead sites are left on purpose, all of them test titles (see the list below).
One more file: a
patchchangeset for@objectstack/service-datasource, because the rewritten docblocks ship (see Changeset below).Census:
service-datasource, before and afterInstrument (A1). The gate's own
node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count below is itsallocated-but-absentfindings underpackages/services/service-datasource/. Each run counts as a reading only because its board frontier equals the newest issue number, read by a separate request just before and just after the run.allocated-but-absent6981abfd2, run 2026-09-29T17:02:34Z to 17:06:27Zf5ec6bacd, run 17:18:37Z to 17:22:33ZThe before count matches the seat's census at the claim (44 sites in 9 files, at
6bff748b). The whole-repo drop is 44, exactly this diff's census sites. Theresolvestally is 32,909 in both runs, andresolves-as-pull-request(1,984) andcross-repo-unjudged(994) did not move either. The after run was taken onf5ec6bacd; the head265dc6861adds only the changeset. No run was truncated or discarded: all four enumerations in this stage (two census runs and the two supplementary boards below) read 186 pages at the newest frontier.Supplementary instrument, the whole scope. The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported
extractCitations(whole-file and comment-prose projections) andclassifyCitationover every.tsfile underservice-datasource/src(61 files). It uses one board for both trees, enumerated by the gate's ownenumerateBoardat 17:22:42Z (186 pages, frontier #20686, equal to the newest).6981abfd2f5ec6bacdIts src-comment column equals the census's 44, which is the control on the second instrument. The 646 resolving and 57 pull-request citations are the same in both readings, and the drop of 75 citations is exactly the rewritten sites. An earlier board (17:07:08Z, frontier #20685) gave the same base reading. A third, raw reading (every
#followed by digits, judged against the same board, whatever surrounds it) finds 88 dead occurrences before and 13 after, and its residue equals the gate's residue site for site.Per-number table
Sites and files count every dead occurrence in scope at the base (comments and strings, tests included).
rewritten / leftcounts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject.#626868f5eccb1: the libSQL/Turso host loader gets one owner (@objectstack/runtime), andMissingDriverPackageErrorbecomes one class across both hosts, becauseserve.tsdecides fatality withinstanceof. The cli and runtime stages' anchor#6345e2798fab7: one driver vocabulary;mongorenamed tomongodb,tursomade a full builtin with a contract, and this factory's dispatch made exhaustive. The spec stages' anchor#85883dede582b:external.credentialsRef(and only it) allowed onschemaMode: 'managed';#8588was that pull request, and this is its squash commit#869672050cc47, a boundcredentialsRefreaches the mysql client on the DSN branch as{ uri, password }(5 sites);90a12fb18, the mongodb DSN branch carries it inoptions.authbeside an unmodified url (5 sites). The spec stage's anchor for the mongo half#8873096106522: a boundcredentialsRefreaches the postgres SERVER on the DSN branch;connectionStringis dropped andpggets its own parse of the url with the credential attached. The spec stage's anchor#8874d70428ae7: a declaredsslreaches the mysql client on both branches, in the spellingmysql2accepts ({}, nevertrue). The spec stage's anchor#8876d634e665b:urlUserinfoUsernameexported, the username half of the shared URL userinfo grammar. The spec stage's anchor#904024206416a: a credential in the mongo options passthrough (config.options.auth.password) is refused at publish. The spec stage's anchor#9041d491625c1: a boundcredentialsRefwith a user-less mongoconfig.urlis refused at the one door that sees both halves. The spec stage's anchor#10537e634ecf6a:POST /external/validatescoped to the URL's datasource; it addsvalidateDatasource. The rest and runtime stages' anchor#1096229d067646: one live introspection per datasource per validation sweep, memoised per call and never per instance (its message names#10962)#11166735f5c709: an unreachable remote is the newunreachablediff kind, notmissing_table. The runtime stage's anchor#1201077b91bdb4:ConnectionEngineLikederived from the engine contract, andregisterDriverstops promising it accepts any value. The runtime stage's anchor#122488425c17cc: the ruled engine members adopted ontoIDataEngine, the datasource-lifecycle trio among them. The spec stage's anchor#12943090f2302e: the guarded optional-driver loads declared as optional peers of this package. The cli and runtime stages' anchor#132796a180e42d: a failed permission-store read raisesAuthzStoreUnavailableError(503) instead of reading as zero grants; its message carries the 2026-08-30 ruling, and it moveddriver-error-classification.tsinto@objectstack/types. The anchor of stage 2 and of the rest, runtime and types stagesEvery cited sha matches exactly one commit (
git rev-parse --disambiguate, count 1 for each of the 17), and every one is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 17; the history is complete,--is-shallow-repositoryfalse, 15,110 commits). Where an earlier stage already anchored a number, this stage reuses that anchor after checking it against this package's lines. New to the sweep here:72050cc47(the mysql half of#8696),3dede582band29d067646.Wordings to check
#8696's two halves. The mysql arm's lines (default-datasource-driver-factory.ts:718,:824, andbound-secret-dsn-branches.test.ts:4,mysql-dsn-ssl.test.ts:189,:263) cite72050cc47; the mongo arm's lines (default-datasource-driver-factory.ts:926,:954,:1243,datasource-credential-migration.ts:182, and the headingbound-secret-dsn-branches.test.ts:72, 「the mongodb half, added second」) cite90a12fb18.datasource-connection-service.ts:95-96. 「the inventory that filed that card」 lost its referent with the number, so it now says 「the inventory that filed its card」, the card behind77b91bdb4(1 reflow line).datasource-connection-service.ts:100-101. 「IDataEngine contract adoption ruled by #11833: declare resolveEffectiveDatasource + getDriverForObject (optional members), and give getObject a real return contract #12248 adjudicated all three onto IDataEngine」 became 「Commit 8425c17 adopted all three onto IDataEngine per the ruling」: the ruling decided and the commit carried it out, as its changeset says (1 reflow line).default-datasource-driver-factory.ts:412andmysql-dsn-ssl.test.ts:40. 「the one A declaredsslblock is silently dropped on the mysql arm's DSN branch (postgres honours it there) #8874 describes as honouring」 became 「the one commit d70428a's card describes as honouring」, since the words quote the card, not the commit.default-datasource-driver-factory.ts:736. 「the falsy-value note under A declaredsslblock is silently dropped on the mysql arm's DSN branch (postgres honours it there) #8874 below」 points at the heading at:767, which now carriescommit d70428ae7, so the pointer names the same anchor.postgres-dsn-bound-secret.test.ts:223. 「the authoring door (Nothing refuses the contradictory pair "external.credentialsRefbound + aconfig.urlnaming no user" — the binding is a silent no-op at connect #9041), which this card lands before」 became 「(commit d491625), which landed after this pin」.096106522(this file's commit) is an ancestor ofd491625c1, both on 2026-08-16.external-datasource-service.test.ts:444. 「The card's measured defect」 became 「Its card's measured defect」, the card behind735f5c709;:588「the pre-POST /datasources/:name/external/validate introspects every federated datasource, not only :name — the route filters validateAll() output after the work is done #10537 route」 became 「the route … before commit e634ecf」.datasource-admin-service.test.ts:674. 「Before PR feat(spec): allow external.credentialsRef (and only it) on schemaMode 'managed' #8588」 became 「Before commit 3dede58」, the squash commit of that pull request, which answers 404.datasource-connection-service.ts:96,:101,default-datasource-driver-factory.ts:825,:826.The 13 sites left
describe/ittitles, which are string tokens, left as stages 1 to 4 left theirs:admin-routes-authz-outage-envelope.test.ts:158(#13279);admin-routes-tenancy-posture-admission.test.ts:557(#13279);bound-secret-dsn-branches.test.ts:136,:244(#8696);connection-engine-like-contract.test.ts:21(#12010);datasource-config-redaction.test.ts:406(#9040);datasource-credential-migration.test.ts:226(#9040);external-datasource-service.test.ts:453(#11166),:690(#10962);mysql-dsn-ssl.test.ts:165,:324(#8874),:260(#8696);postgres-dsn-bound-secret.test.ts:160(#8873).Mechanical guard: no code token moves
The guard compares the TypeScript parser's leaf nodes, with comments as trivia and JSDoc nodes never visited, base
6981abfd2against head. Template literals are therefore read in context. It ran over all 23 touched.tsfiles.default-datasource-driver-factory.ts(Lazy + caught exactly liketoLazy and caught exactly like): 0 files changed, as expected (exit 0).default-datasource-driver-factory.ts(const url = resolveTursoUrl(spec);given a trailing?? undefined): DIFFER (exit 1).mysql-dsn-ssl.test.ts:165): DIFFER (exit 1).Every mutation went through
scripts/ablation-replace.mjs, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (8c164aa6178f,2feeaeb0411d), withgit diff HEADempty and a clean tree afterwards. A first draft of the guard used the bare scanner, which loses template context and reported token changes inside comments; it was replaced by the parser walk before any reading was taken from it.Changeset
This change ships bytes, so a
patchchangeset for@objectstack/service-datasource(.changeset/20596-service-datasource-provenance-anchors.md) is included. It says only that the provenance comments were re-anchored, in stages 3 and 4's words.Measured on the built package (A3):
files[]isdist,README.mdandCHANGELOG.md. After the build, the rewritten comments reachdist:6a180e42d,e2798fab7ande634ecf6aonce, and29d067646three times, in each ofdist/index.d.ts,index.d.cts,index.jsandindex.cjs;68f5eccb14 times,090f2302etwice, and77b91bdb4and8425c17cconce each, in both declaration files. Positive control: the unchanged line 「registerDatasourceDef,markDatasourceUnavailable,」 beside the shipped rewrite atdatasource-connection-service.ts:95is found once inindex.d.ts. A never-written negative phrase appears nowhere indist. No dead number of the 16 is left anywhere indist.Gates (head
265dc6861)pnpm check:issue-citations(self-test) exits 0.node scripts/check-issue-citations.mjsexits 0: the diff-scoped run judged 1 citation (#12482), and it resolves.pnpm check:doc-authoringexits 0, with the sibling-package prose ids at their baseline and no growth.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat265dc6861derived 63 commands: all 54 derived at dispatch, pluscheck:duration-unit-keys,check:dispatcher-error-vocabulary,check:engine-double-contract,check:logger-receiver-detach,check:objectql-double-limit,check:query-options-erasure,check:type-check-coverage,check:type-check-debtandcheck:where-matcher. Each ran with its exit code captured before any pipe, and all 63 exit 0.--ran, fed each command with its exit code, reports 63 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A fullturbo run buildof./packages/*and./packages/*/*ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace.node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casingandpnpm check:filter-alias-parity, each exit 0.pnpm --filter @objectstack/service-datasource test: 34 files pass and 693 tests pass. That is every test file in the package, the 14 touched ones included.pnpm --filter @objectstack/service-datasource typecheckexits 0. Itstsconfig.jsonincludes all ofsrc, and--listFilesshows all 61 files undersrc/, the 34 test files included, and all 23 touched files in the program.eslint --no-inline-config --format jsonover the 23 touched.tsfiles gives 23 files, 0 errors and 0 warnings. All 23 are in eslint's own population (isPathIgnoredis false for each).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-widepnpm lintis CI's run.pnpm check:nul-bytesexits 0, and a raw scan of the 24 changed files for control bytes finds none.Acceptance notes
CITATION_RErefuses a hyphen after the digits and a/before the#(check-issue-citations closeout (extractor spellings):CITATION_RErefuses a hyphen after the digits, so a dead#N-wordcitation (#13398-class) is invisible to the diff gate and to the census #20636). In this package there is no#N-wordspelling at all. There are 7#A/#Blines (admin-routes.ts:28,datasource-route-ledger.ts:159,turso-driver-config.ts:132,external-introspection-seam.test.ts:14,:102,:163,turso-bound-secret-authoring.test.ts:8), and every second number on them is live:#10998,#4251and#4249are issues, and#8078,#4176and#4202are pull requests. So nothing there needed rewriting. The raw scan above, which sees both spellings, agrees.#13279→6a180e42d;#12010→77b91bdb4;#6345→e2798fab7;#6268→68f5eccb1;#12943→090f2302e;#8696→72050cc47(mysql) or90a12fb18(mongodb);#8873→096106522;#8874→d70428ae7;#10962→29d067646.origin/main(14f80e239, read at 17:27Z). None touchesservice-datasource,scripts/or.changeset/config.json, so there was no merge.Generated by Claude Code